From df7257d1b39f51a7e00a495f0d4a2366f0e21f7d Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sun, 14 May 2023 22:04:36 -0400
Subject: fixed webpage link following by adding a presData for the current URL
 to all embedded docs.  fixed getView() in showDocuments to not get called
 with the proper anchors. changed unrendered MARKERs to CONFIGs. changed
 anchors to Configs or Markers as appropriate.

---
 .../collectionFreeForm/CollectionFreeFormView.tsx     | 19 +++++++------------
 1 file changed, 7 insertions(+), 12 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 4d18ebeea..5ac444147 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1147,7 +1147,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @action
     nudge = (x: number, y: number, nudgeTime: number = 500) => {
         const collectionDoc = this.props.docViewPath().lastElement().rootDoc;
-        if (collectionDoc?._viewType !== CollectionViewType.Freeform || collectionDoc._freeform_ !== undefined) {
+        if (collectionDoc?._type_collection !== CollectionViewType.Freeform || collectionDoc._freeform_ !== undefined) {
             this.setPan(
                 NumCast(this.layoutDoc[this.panXFieldKey]) + ((this.props.PanelWidth() / 2) * x) / this.zoomScaling(), // nudge x,y as a function of panel dimension and scale
                 NumCast(this.layoutDoc[this.panYFieldKey]) + ((this.props.PanelHeight() / 2) * -y) / this.zoomScaling(),
@@ -1541,8 +1541,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
         // create an anchor that saves information about the current state of the freeform view (pan, zoom, view type)
-        const anchor = Docs.Create.CollectionAnchorDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._viewType), unrendered: true, presTransition: 500, annotationOn: this.rootDoc });
-        PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: !this.Document._isGroup, viewType: true, filters: true } }, this.rootDoc);
+        const anchor = Docs.Create.CollectionConfigDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._type_collection), layout_unrendered: true, presTransition: 500, annotationOn: this.rootDoc });
+        PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: !this.Document._isGroup, type_collection: true, filters: true } }, this.rootDoc);
 
         if (addAsAnnotation) {
             if (Cast(this.dataDoc[this.props.fieldKey + '_annotations'], listSpec(Doc), null) !== undefined) {
@@ -1851,10 +1851,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     incrementalRender = action(() => {
         if (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath())) {
-            const unrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id]));
+            const layout_unrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id]));
             const loadIncrement = 5;
-            for (var i = 0; i < Math.min(unrendered.length, loadIncrement); i++) {
-                this._renderCutoffData.set(unrendered[i][Id] + '', true);
+            for (var i = 0; i < Math.min(layout_unrendered.length, loadIncrement); i++) {
+                this._renderCutoffData.set(layout_unrendered[i][Id] + '', true);
             }
         }
         this.childDocs.some(doc => !this._renderCutoffData.get(doc[Id])) && setTimeout(this.incrementalRender, 1);
@@ -1991,12 +1991,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 onDragOver={e => e.preventDefault()}
                 onContextMenu={this.onContextMenu}
                 style={{
-                    pointerEvents:
-                        this.props.Document.type === DocumentType.MARKER
-                            ? 'none' // bcz: ugh.. this is here to prevent markers, which render as freeform views, from grabbing events -- need a better approach.
-                            : SnappingManager.GetIsDragging() && this.childDocs.includes(DragManager.docsBeingDragged.lastElement())
-                            ? 'all'
-                            : (this.props.pointerEvents?.() as any),
+                    pointerEvents: SnappingManager.GetIsDragging() && this.childDocs.includes(DragManager.docsBeingDragged.lastElement()) ? 'all' : (this.props.pointerEvents?.() as any),
                     textAlign: this.isAnnotationOverlay ? 'initial' : undefined,
                     transform: `scale(${this.nativeDimScaling || 1})`,
                     width: `${100 / (this.nativeDimScaling || 1)}%`,
-- 
cgit v1.2.3-70-g09d2


From 46cf6c823ca8ab628cd8c5bd7fdfe8945344a014 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 16 May 2023 14:50:29 -0400
Subject: fixed bugs with goldenlayout dragging and undoing.  fixed searching
 for filter values in sidebars.  Stopped creating empty list for collections
 when datafield() is accessed because it messes up undo of a collection. fixed
 tab title editing.  from marquee.  Added UndoStack UI and additional naming
 support in code.

---
 src/client/documents/Documents.ts                  | 12 ++--
 src/client/goldenLayout.js                         |  2 +-
 src/client/util/CurrentUserUtils.ts                | 44 +++++++-------
 src/client/util/DragManager.ts                     |  6 +-
 src/client/util/LinkFollower.ts                    |  2 +-
 src/client/util/SelectionManager.ts                |  2 +-
 src/client/util/UndoManager.ts                     | 53 ++++++++++++-----
 src/client/views/ContextMenuItem.tsx               |  5 +-
 src/client/views/DocumentDecorations.tsx           | 46 +++++----------
 src/client/views/EditableView.tsx                  |  2 +
 src/client/views/FilterPanel.tsx                   |  9 ++-
 src/client/views/GlobalKeyHandler.ts               | 10 ++--
 src/client/views/MainView.tsx                      |  8 +--
 src/client/views/PropertiesButtons.tsx             |  6 +-
 .../views/PropertiesDocBacklinksSelector.tsx       |  2 +-
 src/client/views/PropertiesDocContextSelector.tsx  |  2 +-
 src/client/views/PropertiesView.tsx                | 32 +++++-----
 src/client/views/SidebarAnnos.scss                 | 14 +++--
 src/client/views/SidebarAnnos.tsx                  | 15 ++++-
 src/client/views/UndoStack.tsx                     | 30 ++++++++++
 .../views/collections/CollectionDockingView.tsx    | 63 ++++++++++----------
 .../collections/CollectionNoteTakingViewColumn.tsx |  5 +-
 .../collections/CollectionStackedTimeline.tsx      |  3 +-
 .../CollectionStackingViewFieldColumn.tsx          |  4 +-
 src/client/views/collections/CollectionSubView.tsx | 13 ++--
 src/client/views/collections/TabDocView.tsx        | 21 ++++---
 src/client/views/collections/TreeView.tsx          |  4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 -
 .../collections/collectionFreeForm/MarqueeView.tsx | 26 +++-----
 .../collectionSchema/CollectionSchemaView.tsx      | 32 +++++-----
 .../collectionSchema/SchemaColumnHeader.tsx        |  2 -
 .../collections/collectionSchema/SchemaRowBox.tsx  | 10 ++--
 .../collectionSchema/SchemaTableCell.tsx           |  2 +-
 src/client/views/linking/LinkMenu.tsx              |  2 +-
 src/client/views/nodes/AudioBox.tsx                |  2 +-
 src/client/views/nodes/DocumentLinksButton.tsx     | 20 +++----
 src/client/views/nodes/DocumentView.tsx            | 57 ++++++++++--------
 src/client/views/nodes/LinkDocPreview.tsx          |  3 +-
 src/client/views/nodes/button/FontIconBox.tsx      | 60 ++++++++-----------
 .../views/nodes/formattedText/FormattedTextBox.tsx | 69 +++++++++++-----------
 src/client/views/nodes/trails/PresBox.tsx          |  9 ++-
 src/client/views/nodes/trails/PresElementBox.tsx   | 13 ++--
 src/client/views/pdf/AnchorMenu.tsx                | 11 ++--
 src/client/views/search/SearchBox.tsx              |  2 +
 src/fields/Doc.ts                                  | 18 ++++--
 src/fields/util.ts                                 | 38 +++++++-----
 46 files changed, 421 insertions(+), 372 deletions(-)
 create mode 100644 src/client/views/UndoStack.tsx

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 2151d0ec7..795d60b48 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -24,7 +24,7 @@ import { DirectoryImportBox } from '../util/Import & Export/DirectoryImportBox';
 import { FollowLinkScript } from '../util/LinkFollower';
 import { LinkManager } from '../util/LinkManager';
 import { ScriptingGlobals } from '../util/ScriptingGlobals';
-import { undoBatch, UndoManager } from '../util/UndoManager';
+import { undoable, undoBatch, UndoManager } from '../util/UndoManager';
 import { CollectionDockingView } from '../views/collections/CollectionDockingView';
 import { DimUnit } from '../views/collections/collectionMulticolumn/CollectionMulticolumnView';
 import { CollectionView } from '../views/collections/CollectionView';
@@ -249,7 +249,7 @@ export class DocumentOptions {
     contextMenuIcons?: List<string>;
     defaultDoubleClick?: 'ignore' | 'default'; // ignore double clicks, or deafult (undefined) means open document full screen
     waitForDoubleClickToClick?: 'always' | 'never' | 'default'; // whether a click function wait for double click to expire.  'default' undefined = wait only if there's a click handler, "never" = never wait, "always" = alway wait
-    dontUndo?: boolean; // whether button clicks should be undoable (this is set to true for Undo/Redo/and sidebar buttons that open the siebar panel)
+    dontUndo?: boolean; // whether button clicks should be undoable ( true for Undo/Redo/and sidebar) AND whether modifications to document are undoable (true for linearview menu buttons to prevent open/close from entering undo stack)
     layout?: string | Doc; // default layout string for a document
     contentPointerEvents?: string; // 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
     childLimitHeight?: number; // whether to limit the height of collection children.  0 - means  height can be no bigger than width
@@ -1532,7 +1532,7 @@ export namespace DocUtils {
                 description: 'Quick Notes',
                 subitems: DocListCast((Doc.UserDoc()['template-notes'] as Doc).data).map((note, i) => ({
                     description: ':' + StrCast(note.title),
-                    event: undoBatch((args: { x: number; y: number }) => {
+                    event: undoable((args: { x: number; y: number }) => {
                         const textDoc = Docs.Create.TextDocument('', {
                             _width: 200,
                             x,
@@ -1546,7 +1546,7 @@ export namespace DocUtils {
                             textDoc[pivotField] = pivotValue;
                         }
                         docTextAdder(textDoc);
-                    }),
+                    }, 'create quick note'),
                     icon: StrCast(note.icon) as IconProp,
                 })) as ContextMenuProps[],
                 icon: 'sticky-note',
@@ -1557,7 +1557,7 @@ export namespace DocUtils {
             .filter(doc => doc && doc !== Doc.UserDoc().emptyTrail && doc !== Doc.UserDoc().emptyDataViz)
             .map((dragDoc, i) => ({
                 description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''),
-                event: undoBatch((args: { x: number; y: number }) => {
+                event: undoable((args: { x: number; y: number }) => {
                     const newDoc = DocUtils.copyDragFactory(dragDoc);
                     if (newDoc) {
                         newDoc.author = Doc.CurrentUserEmail;
@@ -1570,7 +1570,7 @@ export namespace DocUtils {
                         }
                         docAdder?.(newDoc);
                     }
-                }),
+                }, StrCast(dragDoc.title)),
                 icon: Doc.toIcon(dragDoc),
             })) as ContextMenuProps[];
         ContextMenu.Instance.addItem({
diff --git a/src/client/goldenLayout.js b/src/client/goldenLayout.js
index 9cb20d834..843b8bb5f 100644
--- a/src/client/goldenLayout.js
+++ b/src/client/goldenLayout.js
@@ -4740,7 +4740,7 @@
                         newstack._$init();
                         newstack.addChild(this.contentItems[0]);
                     }
-                    correctRowOrCol.addChild(newstack,  insertBefore ? 0 : undefined, true);
+                    correctRowOrCol.addChild(newstack,  !insertBefore ? 0 : undefined, true);
                     newstack.config[dimension] = 50;
                     contentItem.config[dimension] = 50;
                     correctRowOrCol.callDownwards('setSize');
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 26fe8f440..b0a2c7d60 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -363,7 +363,7 @@ export class CurrentUserUtils {
         const menuBtns = CurrentUserUtils.leftSidebarMenuBtnDescriptions(doc).map(({ title, target, icon, scripts, funcs }) => {
             const btnDoc = myLeftSidebarMenu ? DocListCast(myLeftSidebarMenu.data).find(doc => doc.title === title) : undefined;
             const reqdBtnOpts:DocumentOptions = {
-                title, icon, target, btnType: ButtonType.MenuButton, isSystem: true, dontUndo: true,  dontRegisterView: true,
+                title, icon, target, btnType: ButtonType.MenuButton, isSystem: true, dontUndo: true, dontRegisterView: true,
                 _width: 60, _height: 60,  _stayInCollection: true, _hideContextMenu: true,
                 _removeDropProperties: new List<string>(["_stayInCollection"]),
             };
@@ -598,12 +598,12 @@ export class CurrentUserUtils {
                             CurrentUserUtils.createToolButton(opts), scripts, funcs);
         
         const btnDescs = [// setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet
-            { scripts: { onClick: "undo()"}, opts: { title: "undo", icon: "undo-alt", toolTip: "Click to undo" }},
-            { scripts: { onClick: "redo()"}, opts: { title: "redo", icon: "redo-alt", toolTip: "Click to redo" }},
-            { scripts: { },                  opts: { title: "linker", icon: "linkui", toolTip: "link started"}},
-            { scripts: { },                  opts: { title: "currently playing", icon: "currentlyplayingui", toolTip: "currently playing media"}},
+            { scripts: { onClick: "undo()"},      opts: { title: "undo", icon: "undo-alt", toolTip: "Click to undo" }},
+            { scripts: { onClick: "redo()"},      opts: { title: "redo", icon: "redo-alt", toolTip: "Click to redo" }},
+            { scripts: { },                       opts: { title: "linker", icon: "linkui", toolTip: "link started"}},
+            { scripts: { },                       opts: { title: "currently playing", icon: "currentlyplayingui", toolTip: "currently playing media"}},
         ];
-        const btns = btnDescs.map(desc => dockBtn({_width: 30, _height: 30, dontUndo: true, _stayInCollection: true, ...desc.opts}, desc.scripts));
+        const btns = btnDescs.map(desc => dockBtn({_width: 30, _height: 30, defaultDoubleClick: 'ignore', dontUndo: true, _stayInCollection: true, ...desc.opts}, desc.scripts));
         const dockBtnsReqdOpts:DocumentOptions = {
             title: "docked buttons", _height: 40, flexGap: 0, boxShadow: "standard", childDropAction: 'embed',
             childDontRegisterViews: true, linearViewIsExpanded: true, linearViewExpandable: true, ignoreClick: true
@@ -632,21 +632,21 @@ export class CurrentUserUtils {
     }
     static textTools():Button[] {
         return [
-            { title: "Font",    toolTip: "Font", width: 100,     btnType: ButtonType.DropdownList,                      toolType:"font",     ignoreClick: true,   scripts: {script:  '{ return setFontAttr(self.toolType, value, _readOnly_);}'},
+            { title: "Font",     toolTip: "Font", width: 100,     btnType: ButtonType.DropdownList,                      toolType:"font",     ignoreClick: true,   scripts: {script:  '{ return setFontAttr(self.toolType, value, _readOnly_);}'},
               btnList: new List<string>(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]) },
-            { title: "Size",    toolTip: "Font size (%size)",    btnType: ButtonType.NumberDropdownButton, width: 75,           toolType:"fontSize", ignoreClick: true,   scripts: {script:  '{ return setFontAttr(self.toolType, value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 0 },
-            { title: "Color",   toolTip: "Font color (%color)",  btnType: ButtonType.ColorButton,  icon: "font",        toolType:"fontColor",ignoreClick: true,   scripts: {script:  '{ return setFontAttr(self.toolType, value, _readOnly_);}'}},
+            { title: "Font Size",toolTip: "Font size (%size)",    btnType: ButtonType.NumberDropdownButton, width: 75,           toolType:"fontSize", ignoreClick: true,   scripts: {script:  '{ return setFontAttr(self.toolType, value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 0 },
+            { title: "Color",    toolTip: "Font color (%color)",  btnType: ButtonType.ColorButton,  icon: "font",        toolType:"fontColor",ignoreClick: true,   scripts: {script:  '{ return setFontAttr(self.toolType, value, _readOnly_);}'}},
             { title: "Highlight",toolTip:"Font highlight",       btnType: ButtonType.ColorButton,  icon: "highlighter", toolType:"highlight",ignoreClick: true,   scripts: {script:  '{ return setFontAttr(self.toolType, value, _readOnly_);}'},funcs: {hidden: "IsNoviceMode()"} },
-            { title: "Bold",    toolTip: "Bold (Ctrl+B)",        btnType: ButtonType.ToggleButton, icon: "bold",        toolType:"bold",                          scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
-            { title: "Italic",  toolTip: "Italic (Ctrl+I)",      btnType: ButtonType.ToggleButton, icon: "italic",      toolType:"italics",                       scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
-            { title: "Under",   toolTip: "Underline (Ctrl+U)",   btnType: ButtonType.ToggleButton, icon: "underline",   toolType:"underline",                     scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
-            { title: "Bullets", toolTip: "Bullet List",          btnType: ButtonType.ToggleButton, icon: "list",        toolType:"bullet",                        scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
-            { title: "#",       toolTip: "Number List",          btnType: ButtonType.ToggleButton, icon: "list-ol",     toolType:"decimal",                       scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
-            { title: "Left",    toolTip: "Left align (Cmd-[)",   btnType: ButtonType.ToggleButton, icon: "align-left",  toolType:"left",                          scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}' }},
-            { title: "Center",  toolTip: "Center align (Cmd-\\)",btnType: ButtonType.ToggleButton, icon: "align-center",toolType:"center",                        scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
-            { title: "Right",   toolTip: "Right align (Cmd-])",  btnType: ButtonType.ToggleButton, icon: "align-right", toolType:"right",                         scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
-            { title: "Dictate", toolTip: "Dictate",              btnType: ButtonType.ToggleButton, icon: "microphone",  toolType:"dictation",                     scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'}},
-            { title: "NoLink",  toolTip: "Auto Link",            btnType: ButtonType.ToggleButton, icon: "link",        toolType:"noAutoLink", expertMode:true,   scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'}, funcs: {hidden: 'IsNoviceMode()'}},
+            { title: "Bold",     toolTip: "Bold (Ctrl+B)",        btnType: ButtonType.ToggleButton, icon: "bold",        toolType:"bold",                          scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
+            { title: "Italic",   toolTip: "Italic (Ctrl+I)",      btnType: ButtonType.ToggleButton, icon: "italic",      toolType:"italics",                       scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
+            { title: "Under",    toolTip: "Underline (Ctrl+U)",   btnType: ButtonType.ToggleButton, icon: "underline",   toolType:"underline",                     scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
+            { title: "Bullets",  toolTip: "Bullet List",          btnType: ButtonType.ToggleButton, icon: "list",        toolType:"bullet",                        scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
+            { title: "#",        toolTip: "Number List",          btnType: ButtonType.ToggleButton, icon: "list-ol",     toolType:"decimal",                       scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
+            { title: "Left",     toolTip: "Left align (Cmd-[)",   btnType: ButtonType.ToggleButton, icon: "align-left",  toolType:"left",                          scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}' }},
+            { title: "Center",   toolTip: "Center align (Cmd-\\)",btnType: ButtonType.ToggleButton, icon: "align-center",toolType:"center",                        scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
+            { title: "Right",    toolTip: "Right align (Cmd-])",  btnType: ButtonType.ToggleButton, icon: "align-right", toolType:"right",                         scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
+            { title: "Dictate",  toolTip: "Dictate",              btnType: ButtonType.ToggleButton, icon: "microphone",  toolType:"dictation",                     scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'}},
+            { title: "NoLink",   toolTip: "Auto Link",            btnType: ButtonType.ToggleButton, icon: "link",        toolType:"noAutoLink", expertMode:true,   scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'}, funcs: {hidden: 'IsNoviceMode()'}},
             // { title: "Strikethrough", tooltip: "Strikethrough", btnType: ButtonType.ToggleButton, icon: "strikethrough", scripts: {onClick:: 'toggleStrikethrough()'}},
             // { title: "Superscript", tooltip: "Superscript", btnType: ButtonType.ToggleButton, icon: "superscript", scripts: {onClick:: 'toggleSuperscript()'}},
             // { title: "Subscript", tooltip: "Subscript", btnType: ButtonType.ToggleButton, icon: "subscript", scripts: {onClick:: 'toggleSubscript()'}},
@@ -711,7 +711,7 @@ export class CurrentUserUtils {
         const reqdOpts:DocumentOptions = {
             ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit,
             backgroundColor: params.backgroundColor ??"transparent", ///  a bit hacky. if an onClick is specified, then assume a toggle uses onClick to get the backgroundColor (see below).  Otherwise, assume a transparent background
-            color: Colors.WHITE, isSystem: true, dontUndo: true, 
+            color: Colors.WHITE, isSystem: true, //dontUndo: true, 
             _nativeWidth: params.width ?? 30, _width: params.width ?? 30,
             _height: 30, _nativeHeight: 30, linearBtnWidth: params.linearBtnWidth,
             toolType: params.toolType, expertMode: params.expertMode,
@@ -727,14 +727,14 @@ export class CurrentUserUtils {
 
     /// Initializes all the default buttons for the top bar context menu
     static setupContextMenuButtons(doc: Doc, field="myContextMenuBtns") {
-        const reqdCtxtOpts:DocumentOptions = { title: "context menu buttons", flexGap: 0, childDropAction: 'embed', childDontRegisterViews: true,  linearViewIsExpanded: true, ignoreClick: true, linearViewExpandable: false, _height: 35 };
+        const reqdCtxtOpts:DocumentOptions = { title: "context menu buttons", dontUndo:true, flexGap: 0, childDropAction: 'embed', childDontRegisterViews: true,  linearViewIsExpanded: true, ignoreClick: true, linearViewExpandable: false, _height: 35 };
         const ctxtMenuBtnsDoc = DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), reqdCtxtOpts, undefined);
         const ctxtMenuBtns = CurrentUserUtils.contextMenuTools().map(params => {
             const menuBtnDoc = DocListCast(ctxtMenuBtnsDoc?.data).find(doc => doc.title === params.title);
             if (!params.subMenu) {
                 return this.setupContextMenuButton(params, menuBtnDoc);
             } else {
-                const reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit,
+                const reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, dontUndo: true,
                     childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: params.scripts?.onClick ? false : true,
                     linearViewSubMenu: true, linearViewExpandable: true, };
                 const items = params.subMenu?.map(sub =>
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 070e0f918..e3798233e 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -263,7 +263,7 @@ export namespace DragManager {
 
     // drags a column from a schema view
     export function StartColumnDrag(ele: HTMLElement[], dragData: ColumnDragData, downX: number, downY: number, options?: DragOptions) {
-        StartDrag(ele, dragData, downX, downY, options);
+        StartDrag(ele, dragData, downX, downY, options, undefined, 'Drag Column');
     }
 
     export function SetSnapLines(horizLines: number[], vertLines: number[]) {
@@ -325,10 +325,10 @@ export namespace DragManager {
     export let docsBeingDragged: Doc[] = observable([] as Doc[]);
     export let CanEmbed = false;
     export let DocDragData: DocumentDragData | undefined;
-    export function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void) {
+    export function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void, dragUndoName?:string) {
         if (dragData.dropAction === 'none') return;
         DocDragData = dragData as DocumentDragData;
-        const batch = UndoManager.StartBatch('dragging');
+        const batch = UndoManager.StartBatch(dragUndoName ?? 'document drag');
         eles = eles.filter(e => e);
         CanEmbed = dragData.canEmbed || false;
         if (!dragDiv) {
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index 2812d6c88..f74409e42 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -28,7 +28,7 @@ export class LinkFollower {
     // if the target isn't onscreen, then it will open up the target in the lightbox, or in place
     // depending on the followLinkLocation property of the source (or the link itself as a fallback);
     public static FollowLink = (linkDoc: Opt<Doc>, sourceDoc: Doc, altKey: boolean) => {
-        const batch = UndoManager.StartBatch('follow link click');
+        const batch = UndoManager.StartBatch('Follow Link');
         runInAction(() => (LinkFollower.IsFollowing = true)); // turn off decoration bounds while following links since animations may occur, and DocDecorations is based on screenToLocal which is not always an observable value
         LinkFollower.traverseLink(
             linkDoc,
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index fba0a4f76..0125331ec 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -118,6 +118,6 @@ ScriptingGlobals.add(function SelectionManager_selectedDocType(type: string, exp
     if (type === 'tab') {
         return SelectionManager.Views().lastElement()?.props.renderDepth === 0;
     }
-    let selected = (sel => (checkContext ? DocCast(sel?.context) : sel))(SelectionManager.SelectedSchemaDoc() ?? SelectionManager.Docs().lastElement());
+    let selected = (sel => (checkContext ? DocCast(sel?.embedContainer) : sel))(SelectionManager.SelectedSchemaDoc() ?? SelectionManager.Docs().lastElement());
     return selected?.type === type || selected?.type_collection === type || !type;
 });
diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts
index d0aec45a6..6fef9d660 100644
--- a/src/client/util/UndoManager.ts
+++ b/src/client/util/UndoManager.ts
@@ -3,7 +3,7 @@ import { Without } from '../../Utils';
 
 function getBatchName(target: any, key: string | symbol): string {
     const keyName = key.toString();
-    if (target && target.constructor && target.constructor.name) {
+    if (target?.constructor?.name) {
         return `${target.constructor.name}.${keyName}`;
     }
     return keyName;
@@ -34,6 +34,17 @@ function propertyDecorator(target: any, key: string | symbol) {
     });
 }
 
+export function undoable(fn: (...args: any[]) => any, batchName: string): (...args: any[]) => any {
+    return function () {
+        const batch = UndoManager.StartBatch(batchName);
+        try {
+            return fn.apply(undefined, arguments as any);
+        } finally {
+            batch.end();
+        }
+    };
+}
+
 export function undoBatch(target: any, key: string | symbol, descriptor?: TypedPropertyDescriptor<any>): any;
 export function undoBatch(fn: (...args: any[]) => any): (...args: any[]) => any;
 export function undoBatch(target: any, key?: string | symbol, descriptor?: TypedPropertyDescriptor<any>): any {
@@ -73,15 +84,18 @@ export namespace UndoManager {
     }
     type UndoBatch = UndoEvent[];
 
+    export let undoStackNames: string[] = observable([]);
+    export let redoStackNames: string[] = observable([]);
     export let undoStack: UndoBatch[] = observable([]);
     export let redoStack: UndoBatch[] = observable([]);
     let currentBatch: UndoBatch | undefined;
-    export let batchCounter = 0;
+    export let batchCounter = observable.box(0);
     let undoing = false;
     let tempEvents: UndoEvent[] | undefined = undefined;
 
-    export function AddEvent(event: UndoEvent): void {
-        if (currentBatch && batchCounter && !undoing) {
+    export function AddEvent(event: UndoEvent, value?: any): void {
+        if (currentBatch && batchCounter.get() && !undoing) {
+            console.log('                         '.slice(0, batchCounter.get()) + 'UndoEvent : ' + event.prop + ' = ' + value);
             currentBatch.push(event);
             tempEvents?.push(event);
         }
@@ -135,11 +149,13 @@ export namespace UndoManager {
 
         private dispose = (cancel: boolean) => {
             if (this.disposed) {
-                throw new Error('Cannot dispose an already disposed batch');
+                console.log('WARNING: undo batch already disposed');
+                return false;
+            } else {
+                this.disposed = true;
+                openBatches.splice(openBatches.indexOf(this));
+                return EndBatch(this.batchName, cancel);
             }
-            this.disposed = true;
-            openBatches.splice(openBatches.indexOf(this));
-            return EndBatch(cancel);
         };
 
         end = () => this.dispose(false);
@@ -147,22 +163,23 @@ export namespace UndoManager {
     }
 
     export function StartBatch(batchName: string): Batch {
-        // console.log("Start " + batchCounter + " " + batchName);
-        batchCounter++;
-        if (batchCounter > 0 && currentBatch === undefined) {
+        console.log('                          '.slice(0, batchCounter.get()) + 'Start ' + batchCounter + ' ' + batchName);
+        runInAction(() => batchCounter.set(batchCounter.get() + 1));
+        if (currentBatch === undefined) {
             currentBatch = [];
         }
         return new Batch(batchName);
     }
 
-    const EndBatch = action((cancel: boolean = false) => {
-        batchCounter--;
-        // console.log("End " + batchCounter);
-        if (batchCounter === 0 && currentBatch?.length) {
-            // console.log("------ended----")
+    const EndBatch = action((batchName: string, cancel: boolean = false) => {
+        runInAction(() => batchCounter.set(batchCounter.get() - 1));
+        console.log('                          '.slice(0, batchCounter.get()) + 'End ' + batchName + ' (' + currentBatch?.length + ')');
+        if (batchCounter.get() === 0 && currentBatch?.length) {
             if (!cancel) {
                 undoStack.push(currentBatch);
+                undoStackNames.push(batchName ?? '???');
             }
+            redoStackNames.length = 0;
             redoStack.length = 0;
             currentBatch = undefined;
             return true;
@@ -204,6 +221,7 @@ export namespace UndoManager {
             return;
         }
 
+        const names = undoStackNames.pop();
         const commands = undoStack.pop();
         if (!commands) {
             return;
@@ -215,6 +233,7 @@ export namespace UndoManager {
         }
         undoing = false;
 
+        redoStackNames.push(names ?? '???');
         redoStack.push(commands);
     });
 
@@ -223,6 +242,7 @@ export namespace UndoManager {
             return;
         }
 
+        const names = redoStackNames.pop();
         const commands = redoStack.pop();
         if (!commands) {
             return;
@@ -234,6 +254,7 @@ export namespace UndoManager {
         }
         undoing = false;
 
+        undoStackNames.push(names ?? '???');
         undoStack.push(commands);
     });
 }
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index e87d2046b..33f250986 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -40,10 +40,7 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
     handleEvent = async (e: React.MouseEvent<HTMLDivElement>) => {
         if ('event' in this.props) {
             this.props.closeMenu?.();
-            let batch: UndoManager.Batch | undefined;
-            if (this.props.undoable !== false) {
-                batch = UndoManager.StartBatch(`Context menu event: ${this.props.description}`);
-            }
+            const batch = this.props.undoable !== false ? UndoManager.StartBatch(`Click Menu item: ${this.props.description}`) : undefined;
             await this.props.event({ x: e.clientX, y: e.clientY });
             batch?.end();
         }
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 39073d763..8077b9af1 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -2,23 +2,25 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@material-ui/core';
 import { IconButton } from 'browndash-components';
-import { action, computed, observable, reaction, runInAction } from 'mobx';
+import { action, computed, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import { FaUndo } from 'react-icons/fa';
 import { DateField } from '../../fields/DateField';
 import { AclAdmin, AclEdit, DataSym, Doc, DocListCast, Field, HeightSym, WidthSym } from '../../fields/Doc';
-import { Document } from '../../fields/documentSchemas';
 import { InkField } from '../../fields/InkField';
+import { RichTextField } from '../../fields/RichTextField';
 import { ScriptField } from '../../fields/ScriptField';
 import { Cast, NumCast, StrCast } from '../../fields/Types';
 import { GetEffectiveAcl } from '../../fields/util';
 import { aggregateBounds, emptyFunction, numberValue, returnFalse, setupMoveUpEvents, Utils } from '../../Utils';
 import { Docs } from '../documents/Documents';
 import { DocumentType } from '../documents/DocumentTypes';
+import { DocumentManager } from '../util/DocumentManager';
 import { DragManager } from '../util/DragManager';
+import { LinkFollower } from '../util/LinkFollower';
 import { SelectionManager } from '../util/SelectionManager';
 import { SnappingManager } from '../util/SnappingManager';
-import { undoBatch, UndoManager } from '../util/UndoManager';
+import { UndoManager } from '../util/UndoManager';
 import { CollectionDockingView } from './collections/CollectionDockingView';
 import { CollectionFreeFormView } from './collections/collectionFreeForm';
 import { DocumentButtonBar } from './DocumentButtonBar';
@@ -27,15 +29,11 @@ import { Colors } from './global/globalEnums';
 import { InkingStroke } from './InkingStroke';
 import { InkStrokeProperties } from './InkStrokeProperties';
 import { LightboxView } from './LightboxView';
-import { DocumentView, OpenWhere, OpenWhereMod } from './nodes/DocumentView';
+import { DocumentView, OpenWhereMod } from './nodes/DocumentView';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import { ImageBox } from './nodes/ImageBox';
 import React = require('react');
-import { RichTextField } from '../../fields/RichTextField';
-import { LinkFollower } from '../util/LinkFollower';
 import _ = require('lodash');
-import { DocumentManager } from '../util/DocumentManager';
-import { isUndefined } from 'lodash';
 
 @observer
 export class DocumentDecorations extends React.Component<{ PanelWidth: number; PanelHeight: number; boundsLeft: number; boundsTop: number }, { value: string }> {
@@ -150,7 +148,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                             Doc.SetInPlace(d.rootDoc, titleFieldKey, titleField, true);
                         }
                     }),
-                'title blur'
+                'edit title'
             );
         }
     };
@@ -227,11 +225,6 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
             .filter(v => v && v.props.renderDepth > 0);
         if (forceDeleteOrIconify === false && this._iconifyBatch) return;
         this._deleteAfterIconify = forceDeleteOrIconify || this._iconifyBatch ? true : false;
-        if (!this._iconifyBatch) {
-            this._iconifyBatch = UndoManager.StartBatch('iconifying');
-        } else {
-            forceDeleteOrIconify = false; // can't force immediate close in the middle of iconifying -- have to wait until iconifying completes
-        }
         var iconifyingCount = views.length;
         const finished = action((force?: boolean) => {
             if ((force || --iconifyingCount === 0) && this._iconifyBatch) {
@@ -250,6 +243,12 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                 this._iconifyBatch = undefined;
             }
         });
+        if (!this._iconifyBatch) {
+            this._iconifyBatch = UndoManager.StartBatch(forceDeleteOrIconify ? 'delete selected docs' : 'iconifying');
+        } else {
+            forceDeleteOrIconify = false; // can't force immediate close in the middle of iconifying -- have to wait until iconifying completes
+        }
+
         if (forceDeleteOrIconify) finished(forceDeleteOrIconify);
         else if (!this._deleteAfterIconify) views.forEach(dv => dv.iconify(finished));
     };
@@ -397,7 +396,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
     onRotateDown = (e: React.PointerEvent): void => {
         this._isRotating = true;
         const rcScreen = { X: this.rotCenter[0], Y: this.rotCenter[1] };
-        const rotateUndo = UndoManager.StartBatch('rotatedown');
+        const rotateUndo = UndoManager.StartBatch('drag rotation');
         const selectedInk = SelectionManager.Views().filter(i => i.ComponentView instanceof InkingStroke);
         const centerPoint = this.rotCenter.slice();
         const infos = new Map<Doc, { unrotatedDocPos: { x: number; y: number }; startRotCtr: { x: number; y: number }; accumRot: number }>();
@@ -465,7 +464,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         const bounds = e.currentTarget.getBoundingClientRect();
         this._offX = this._resizeHdlId.toLowerCase().includes('left') ? bounds.right - e.clientX : bounds.left - e.clientX;
         this._offY = this._resizeHdlId.toLowerCase().includes('top') ? bounds.bottom - e.clientY : bounds.top - e.clientY;
-        this._resizeUndo = UndoManager.StartBatch('DocDecs resize');
+        this._resizeUndo = UndoManager.StartBatch('drag resizing');
         this._snapX = e.pageX;
         this._snapY = e.pageY;
         const ffviewSet = new Set<CollectionFreeFormView>();
@@ -770,20 +769,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
 
         const topBtn = (key: string, icon: string, pointerDown: undefined | ((e: React.PointerEvent) => void), click: undefined | ((e: any) => void), title: string) => (
             <Tooltip key={key} title={<div className="dash-tooltip">{title}</div>} placement="top">
-                <div
-                    className={`documentDecorations-${key}Button`}
-                    onContextMenu={e => e.preventDefault()}
-                    onPointerDown={
-                        pointerDown ??
-                        (e =>
-                            setupMoveUpEvents(
-                                this,
-                                e,
-                                returnFalse,
-                                emptyFunction,
-                                undoBatch(e => click!(e))
-                            ))
-                    }>
+                <div className={`documentDecorations-${key}Button`} onContextMenu={e => e.preventDefault()} onPointerDown={pointerDown ?? (e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, e => click!(e)))}>
                     <FontAwesomeIcon icon={icon as any} />
                 </div>
             </Tooltip>
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 6b4132814..7043edcee 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -156,12 +156,14 @@ export class EditableView extends React.Component<EditableProps> {
                 break;
             case ':':
                 if (this.props.menuCallback) {
+                    e.stopPropagation();
                     this.props.menuCallback(e.currentTarget.getBoundingClientRect().x, e.currentTarget.getBoundingClientRect().y);
                     break;
                 }
 
             default:
                 if (this.props.textCallback?.(e.key)) {
+                    e.stopPropagation();
                     this._editing = false;
                     this.props.isEditingCallback?.(false);
                 }
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index 75e0e7c4c..53c1f1018 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -10,6 +10,7 @@ import { UserOptions } from '../util/GroupManager';
 import './FilterPanel.scss';
 import { FieldView } from './nodes/FieldView';
 import { SearchBox } from './search/SearchBox';
+import { undoable } from '../util/UndoManager';
 
 interface filterProps {
     rootDoc: Doc;
@@ -31,7 +32,7 @@ export class FilterPanel extends React.Component<filterProps> {
         return targetView?.ComponentView?.annotationKey ?? targetView?.ComponentView?.fieldKey ?? 'data';
     }
     @computed get targetDocChildren() {
-        return DocListCast(this.targetDoc?.[this.targetDocChildKey] || Doc.ActiveDashboard?.data);
+        return [...DocListCast(this.targetDoc?.[this.targetDocChildKey] || Doc.ActiveDashboard?.data), ...DocListCast(this.targetDoc[Doc.LayoutFieldKey(this.targetDoc) + '_sidebar'])];
     }
 
     @computed get allDocs() {
@@ -89,6 +90,7 @@ export class FilterPanel extends React.Component<filterProps> {
                     const fieldKey = Doc.LayoutFieldKey(t);
                     const annos = !Field.toString(Doc.LayoutField(t) as Field).includes('CollectionView');
                     DocListCast(t[annos ? fieldKey + '_annotations' : fieldKey]).forEach(newdoc => newarray.push(newdoc));
+                    annos && DocListCast(t[fieldKey + '_sidebar']).forEach(newdoc => newarray.push(newdoc));
                 });
                 subDocs = newarray;
             }
@@ -204,7 +206,8 @@ export class FilterPanel extends React.Component<filterProps> {
                                 .find(filter => filter.split(':')[0] === facetHeader)
                                 ?.split(':')[1] ?? '-empty-'
                         }
-                        onKeyDown={e => e.key === 'Enter' && Doc.setDocFilter(this.targetDoc, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match')}
+                        onBlur={undoable(e => Doc.setDocFilter(this.targetDoc, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match'), 'set text filter')}
+                        onKeyDown={e => e.key === 'Enter' && undoable(e => Doc.setDocFilter(this.targetDoc, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match'), 'set text filter')(e)}
                     />
                 );
             case 'checkbox':
@@ -220,7 +223,7 @@ export class FilterPanel extends React.Component<filterProps> {
                                         ?.split(':')[2] === 'check'
                                 }
                                 type={type}
-                                onChange={e => Doc.setDocFilter(this.targetDoc, facetHeader, fval, e.target.checked ? 'check' : 'remove')}
+                                onChange={undoable(e => Doc.setDocFilter(this.targetDoc, facetHeader, fval, e.target.checked ? 'check' : 'remove'), 'set filter')}
                             />
                             {facetValue}
                         </div>
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index b9b92dd2b..625bc760d 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -161,12 +161,10 @@ export class KeyManager {
             case 'delete':
             case 'backspace':
                 if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') {
-                    UndoManager.RunInBatch(() => {
-                        if (LightboxView.LightboxDoc) {
-                            LightboxView.SetLightboxDoc(undefined);
-                            SelectionManager.DeselectAll();
-                        } else DocumentDecorations.Instance.onCloseClick(true);
-                    }, 'backspace');
+                    if (LightboxView.LightboxDoc) {
+                        LightboxView.SetLightboxDoc(undefined);
+                        SelectionManager.DeselectAll();
+                    } else DocumentDecorations.Instance.onCloseClick(true);
                     return { stopPropagation: true, preventDefault: true };
                 }
                 break;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 853f9cace..50f451c0a 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -915,11 +915,11 @@ export class MainView extends React.Component {
         return !SelectionManager.Views().some(dv => dv.rootDoc.freeform_snapLines) ? null : (
             <div className="mainView-snapLines">
                 <svg style={{ width: '100%', height: '100%' }}>
-                    {SnappingManager.horizSnapLines().map(l => (
-                        <line x1="0" y1={l} x2="2000" y2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={'1 1'} />
+                    {SnappingManager.horizSnapLines().map((l, i) => (
+                        <line key={i} x1="0" y1={l} x2="2000" y2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={'1 1'} />
                     ))}
-                    {SnappingManager.vertSnapLines().map(l => (
-                        <line y1="0" x1={l} y2="2000" x2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={'1 1'} />
+                    {SnappingManager.vertSnapLines().map((l, i) => (
+                        <line key={i} y1="0" x1={l} y2="2000" x2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={'1 1'} />
                     ))}
                 </svg>
             </div>
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index a37447505..a5c58c9d2 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -14,7 +14,7 @@ import { DocUtils } from '../documents/Documents';
 import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
 import { LinkManager } from '../util/LinkManager';
 import { SelectionManager } from '../util/SelectionManager';
-import { undoBatch } from '../util/UndoManager';
+import { undoable, undoBatch } from '../util/UndoManager';
 import { Colors } from './global/globalEnums';
 import { InkingStroke } from './InkingStroke';
 import { DocumentView, OpenWhere } from './nodes/DocumentView';
@@ -51,11 +51,11 @@ export class PropertiesButtons extends React.Component<{}, {}> {
                     <div
                         className={`propertiesButtons-linkButton-empty toggle-${StrCast(targetDoc[property]).includes(':hover') ? 'hover' : targetDoc[property] ? 'on' : 'off'}`}
                         onPointerDown={e => e.stopPropagation()}
-                        onClick={undoBatch(() => {
+                        onClick={undoable(() => {
                             if (SelectionManager.Views().length > 1) {
                                 SelectionManager.Views().forEach(dv => (onClick ?? onPropToggle)(dv, dv.rootDoc, property));
                             } else if (targetDoc) (onClick ?? onPropToggle)(undefined, targetDoc, property);
-                        })}>
+                        }, property)}>
                         <FontAwesomeIcon className="documentdecorations-icon" size="lg" icon={icon(BoolCast(targetDoc?.[property])) as any} />
                     </div>
                     <div className="propertiesButtons-title">{label}</div>
diff --git a/src/client/views/PropertiesDocBacklinksSelector.tsx b/src/client/views/PropertiesDocBacklinksSelector.tsx
index 46e6fd188..7b21629da 100644
--- a/src/client/views/PropertiesDocBacklinksSelector.tsx
+++ b/src/client/views/PropertiesDocBacklinksSelector.tsx
@@ -25,7 +25,7 @@ export class PropertiesDocBacklinksSelector extends React.Component<PropertiesDo
 
         if (otherdoc) {
             otherdoc.hidden = false;
-            this.props.addDocTab(Doc.IsDocDataProto(otherdoc) ? Doc.MakeDelegate(otherdoc) : otherdoc, OpenWhere.toggleRight);
+            this.props.addDocTab(Doc.IsDataProto(otherdoc) ? Doc.MakeDelegate(otherdoc) : otherdoc, OpenWhere.toggleRight);
         }
     };
 
diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx
index 12a09196e..e1279c9a7 100644
--- a/src/client/views/PropertiesDocContextSelector.tsx
+++ b/src/client/views/PropertiesDocContextSelector.tsx
@@ -48,7 +48,7 @@ export class PropertiesDocContextSelector extends React.Component<PropertiesDocC
 
     getOnClick = (col: Doc, target: Doc) => {
         if (!this.props.DocView) return;
-        col = Doc.IsDocDataProto(col) ? Doc.MakeDelegate(col) : col;
+        col = Doc.IsDataProto(col) ? Doc.MakeDelegate(col) : col;
         DocFocusOrOpen(Doc.GetProto(this.props.DocView.props.Document), undefined, col);
     };
 
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 19c138a21..297820e37 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -21,7 +21,7 @@ import { LinkManager } from '../util/LinkManager';
 import { SelectionManager } from '../util/SelectionManager';
 import { SharingManager } from '../util/SharingManager';
 import { Transform } from '../util/Transform';
-import { undoBatch, UndoManager } from '../util/UndoManager';
+import { undoable, undoBatch, UndoManager } from '../util/UndoManager';
 import { EditableView } from './EditableView';
 import { FilterPanel } from './FilterPanel';
 import { Colors } from './global/globalEnums';
@@ -538,6 +538,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                     className="inputBox-input"
                     type="text"
                     value={value}
+                    readOnly={true}
                     onChange={e => {
                         setter(e.target.value);
                     }}
@@ -656,21 +657,21 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
         return this.inputBoxDuo(
             'hgt',
             this.shapeHgt,
-            (val: string) => {
+            undoable((val: string) => {
                 if (!isNaN(Number(val))) {
                     this.shapeHgt = val;
                 }
                 return true;
-            },
+            }, 'set height'),
             'H:',
             'wid',
             this.shapeWid,
-            (val: string) => {
+            undoable((val: string) => {
                 if (!isNaN(Number(val))) {
                     this.shapeWid = val;
                 }
                 return true;
-            },
+            }, 'set width'),
             'W:'
         );
     }
@@ -678,21 +679,21 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
         return this.inputBoxDuo(
             'Xps',
             this.shapeXps,
-            (val: string) => {
+            undoable((val: string) => {
                 if (val !== '0' && !isNaN(Number(val))) {
                     this.shapeXps = val;
                 }
                 return true;
-            },
+            }, 'set x coord'),
             'X:',
             'Yps',
             this.shapeYps,
-            (val: string) => {
+            undoable((val: string) => {
                 if (val !== '0' && !isNaN(Number(val))) {
                     this.shapeYps = val;
                 }
                 return true;
-            },
+            }, 'set y coord'),
             'Y:'
         );
     }
@@ -867,7 +868,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     regInput = (key: string, value: any, setter: (val: string) => {}) => {
         return (
             <div className="inputBox">
-                <input className="inputBox-input" type="text" value={value} onChange={e => setter(e.target.value)} />
+                <input className="inputBox-input" type="text" readOnly={true} value={value} onChange={e => setter(e.target.value)} />
                 <div className="inputBox-button">
                     <div className="inputBox-button-up" key="up2" onPointerDown={undoBatch(action(() => this.upDownButtons('up', key)))}>
                         <FontAwesomeIcon icon="caret-up" color="white" size="sm" />
@@ -1228,8 +1229,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
         }
     });
 
-    @undoBatch
-    changeFollowBehavior = action((follow: Opt<string>) => this.sourceAnchor && (this.sourceAnchor.followLinkLocation = follow));
+    changeFollowBehavior = undoable((loc: Opt<string>) => this.sourceAnchor && (this.sourceAnchor.followLinkLocation = loc), 'change follow behavior');
 
     @undoBatch
     changeAnimationBehavior = action((behavior: string) => this.sourceAnchor && (this.sourceAnchor.followLinkAnimEffect = behavior));
@@ -1315,6 +1315,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                 autoComplete={'off'}
                 id="link_relationship_input"
                 value={StrCast(LinkManager.currentLink?.link_relationship)}
+                readOnly={true}
                 onKeyDown={this.onRelationshipKey}
                 onBlur={this.onSelectOutRelationship}
                 onChange={e => this.handlelinkRelationshipChange(e.currentTarget.value)}
@@ -1332,6 +1333,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                 style={{ textAlign: 'left' }}
                 id="link_description_input"
                 value={Field.toString(LinkManager.currentLink?.link_description as any as Field)}
+                readOnly={true}
                 onKeyDown={this.onDescriptionKey}
                 onBlur={this.onSelectOutDesc}
                 onChange={e => this.handleDescriptionChange(e.currentTarget.value)}
@@ -1457,7 +1459,9 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                                             <select style={{ width: '100%', gridColumn: 2 }} onChange={e => this.changeAnimationBehavior(e.currentTarget.value)} value={StrCast(this.sourceAnchor?.followLinkAnimEffect, 'default')}>
                                                 <option value="default">Default</option>
                                                 {[PresEffect.None, PresEffect.Zoom, PresEffect.Lightspeed, PresEffect.Fade, PresEffect.Flip, PresEffect.Rotate, PresEffect.Bounce, PresEffect.Roll].map(effect => (
-                                                    <option value={effect.toString()}>{effect.toString()}</option>
+                                                    <option key={effect.toString()} value={effect.toString()}>
+                                                        {effect.toString()}
+                                                    </option>
                                                 ))}
                                             </select>
                                             <div className="effectDirection" style={{ marginLeft: '10px', display: 'grid', width: 40, height: 36, gridColumn: 3, gridTemplateRows: '12px 12px 12px' }}>
@@ -1567,7 +1571,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                                         <div className="propertiesView-input inline" style={{ display: 'grid', gridTemplateColumns: '78px calc(100% - 108px) 50px' }}>
                                             <p>Zoom %</p>
                                             <div className="ribbon-property" style={{ display: !targZoom ? 'none' : 'inline-flex' }}>
-                                                <input className="presBox-input" style={{ width: '100%' }} type="number" value={zoom} />
+                                                <input className="presBox-input" style={{ width: '100%' }} readOnly={true} type="number" value={zoom} />
                                                 <div className="ribbon-propertyUpDown" style={{ display: 'flex', flexDirection: 'column' }}>
                                                     <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setZoom(String(zoom), 0.1))}>
                                                         <FontAwesomeIcon icon={'caret-up'} />
diff --git a/src/client/views/SidebarAnnos.scss b/src/client/views/SidebarAnnos.scss
index a0506cb3a..d7de2b641 100644
--- a/src/client/views/SidebarAnnos.scss
+++ b/src/client/views/SidebarAnnos.scss
@@ -4,22 +4,26 @@
     overflow: auto;
     flex-flow: row;
     flex-wrap: wrap;
-    .sidebarAnnos-filterTag, .sidebarAnnos-filterTag-active,
-    .sidebarAnnos-filterUser, .sidebarAnnos-filterUser-active {
+    .sidebarAnnos-filterTag,
+    .sidebarAnnos-filterTag-active,
+    .sidebarAnnos-filterUser,
+    .sidebarAnnos-filterUser-active {
         font-weight: bold;
         font-size: 10px;
         padding-left: 5;
         padding-right: 5;
         box-shadow: black 1px 1px 3px;
         border-radius: 5;
-        margin: 2; 
+        margin: 2;
         height: 15;
         background-color: lightgrey;
     }
-    .sidebarAnnos-filterUser, .sidebarAnnos-filterUser-active {
+    .sidebarAnnos-filterUser,
+    .sidebarAnnos-filterUser-active {
         border-radius: 15px;
     }
+    .sidebarAnnos-filterUser-active,
     .sidebarAnnos-filterTag-active {
         background-color: white;
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index e12621f35..c9e52a1db 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -49,13 +49,21 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
         );
         return keys;
     }
+    @computed get allHashtags() {
+        const keys = new Set<string>();
+        DocListCast(this.props.rootDoc[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()))
+            .sort();
+    }
     @computed get allUsers() {
         const keys = new Set<string>();
         DocListCast(this.props.rootDoc[this.sidebarKey]).forEach(doc => keys.add(StrCast(doc.author)));
         return Array.from(keys.keys()).sort();
     }
     get filtersKey() {
-        return '_' + this.sidebarKey + '-docFilters';
+        return '_' + this.sidebarKey + '_docFilters';
     }
 
     anchorMenuClick = (anchor: Doc) => {
@@ -179,9 +187,9 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
     };
     render() {
         const renderTag = (tag: string) => {
-            const active = StrListCast(this.props.rootDoc[this.filtersKey]).includes(`${tag}:${tag}:check`);
+            const active = StrListCast(this.props.rootDoc[this.filtersKey]).includes(`tags:${tag}:check`);
             return (
-                <div key={tag} className={`sidebarAnnos-filterTag${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this.props.rootDoc, tag, tag, 'check', true, this.sidebarKey, e.shiftKey)}>
+                <div key={tag} className={`sidebarAnnos-filterTag${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this.props.rootDoc, 'tags', tag, 'check', true, this.sidebarKey, e.shiftKey)}>
                     {tag}
                 </div>
             );
@@ -216,6 +224,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
                 }}>
                 <div className="sidebarAnnos-tagList" style={{ height: this.filtersHeight() }} onWheel={e => e.stopPropagation()}>
                     {this.allUsers.map(renderUsers)}
+                    {this.allHashtags.map(renderTag)}
                     {Array.from(this.allMetadata.keys())
                         .sort()
                         .map(key => renderMeta(key, this.allMetadata.get(key)))}
diff --git a/src/client/views/UndoStack.tsx b/src/client/views/UndoStack.tsx
new file mode 100644
index 000000000..01e184d6b
--- /dev/null
+++ b/src/client/views/UndoStack.tsx
@@ -0,0 +1,30 @@
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { UndoManager } from '../util/UndoManager';
+import './ScriptingRepl.scss';
+
+@observer
+export class UndoStack extends React.Component {
+    render() {
+        return (
+            <div className="scriptingRepl-outerContainer">
+                <div className="scriptingRepl-commandsContainer" ref={r => r?.scroll({ behavior: 'auto', top: r?.scrollHeight + 20 })} style={{ background: UndoManager.batchCounter.get() ? 'yellow' : undefined }}>
+                    {UndoManager.undoStackNames.map((name, i) => (
+                        <div className="scriptingRepl-resultContainer" key={i}>
+                            <div className="scriptingRepl-commandString">{name.replace(/[^\.]*\./, '')}</div>
+                        </div>
+                    ))}
+                    {Array.from(UndoManager.redoStackNames)
+                        .reverse()
+                        .map((name, i) => (
+                            <div className="scriptingRepl-resultContainer" key={i}>
+                                <div className="scriptingRepl-commandString" style={{ fontWeight: 'bold', color: 'red' }}>
+                                    {name.replace(/[^\.]*\./, '')}
+                                </div>
+                            </div>
+                        ))}
+                </div>
+            </div>
+        );
+    }
+}
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index cce21a3aa..4ae24af60 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -6,7 +6,7 @@ import { Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
-import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types';
+import { ImageCast, NumCast, StrCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
 import { inheritParentAcls } from '../../../fields/util';
 import { emptyFunction, incrementTitleCopy } from '../../../Utils';
@@ -20,14 +20,14 @@ import { SelectionManager } from '../../util/SelectionManager';
 import { undoBatch, UndoManager } from '../../util/UndoManager';
 import { DashboardView } from '../DashboardView';
 import { LightboxView } from '../LightboxView';
+import { OpenWhere, OpenWhereMod } from '../nodes/DocumentView';
+import { OverlayView } from '../OverlayView';
+import { ScriptingRepl } from '../ScriptingRepl';
 import './CollectionDockingView.scss';
 import { CollectionFreeFormView } from './collectionFreeForm';
 import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
 import { TabDocView } from './TabDocView';
 import React = require('react');
-import { OpenWhere, OpenWhereMod } from '../nodes/DocumentView';
-import { OverlayView } from '../OverlayView';
-import { ScriptingRepl } from '../ScriptingRepl';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 @observer
@@ -85,6 +85,7 @@ export class CollectionDockingView extends CollectionSubView() {
 
     tabItemDropped = () => DragManager.CompleteWindowDrag?.(false);
     tabDragStart = (proxy: any, finishDrag?: (aborted: boolean) => void) => {
+        this._flush = this._flush ?? UndoManager.StartBatch('tab move');
         const dashDoc = proxy?._contentItem?.tab?.DashDoc as Doc;
         dashDoc && (DragManager.DocDragData = new DragManager.DocumentDragData([proxy._contentItem.tab.DashDoc]));
         DragManager.CompleteWindowDrag = (aborted: boolean) => {
@@ -92,12 +93,12 @@ export class CollectionDockingView extends CollectionSubView() {
                 proxy._dragListener.AbortDrag();
                 if (this._flush) {
                     this._flush.cancel(); // cancel the undo change being logged
-                    this._flush = undefined;
                     this.setupGoldenLayout(); // restore golden layout to where it was before the drag (this is a no-op when using StartOtherDrag because the proxy dragged item was never in the golden layout)
                 }
                 DragManager.CompleteWindowDrag = undefined;
             }
             finishDrag?.(aborted);
+            setTimeout(this.endUndoBatch, 100);
         };
     };
     @undoBatch
@@ -180,7 +181,6 @@ export class CollectionDockingView extends CollectionSubView() {
     //
     //  Creates a split on any side of the docking view based on the passed input pullSide and then adds the Document to the requested side
     //
-    @undoBatch
     @action
     public static AddSplit(document: Doc, pullSide: OpenWhereMod, stack?: any, panelName?: string, keyValue?: boolean) {
         if (document?._type_collection === CollectionViewType.Docking) return DashboardView.openDashboard(document);
@@ -195,6 +195,8 @@ export class CollectionDockingView extends CollectionSubView() {
         if (!instance) return false;
         const docContentConfig = CollectionDockingView.makeDocumentConfig(document, panelName, undefined, keyValue);
 
+        CollectionDockingView.Instance._flush = CollectionDockingView.Instance._flush ?? UndoManager.StartBatch('Add Split');
+        setTimeout(CollectionDockingView.Instance.endUndoBatch, 100);
         if (!pullSide && stack) {
             stack.addChild(docContentConfig, undefined);
             setTimeout(() => stack.setActiveContentItem(stack.contentItems[stack.contentItems.length - 1]));
@@ -370,16 +372,30 @@ export class CollectionDockingView extends CollectionSubView() {
         !LightboxView.LightboxDoc && cur && this._goldenLayout?.updateSize(cur.getBoundingClientRect().width, cur.getBoundingClientRect().height);
     };
 
+    endUndoBatch = () => {
+        const json = JSON.stringify(this._goldenLayout.toConfig());
+        const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g);
+        const docids = matches?.map(m => m.replace('"documentId":"', '').replace('"', ''));
+        const docs = !docids
+            ? []
+            : docids
+                  .map(id => DocServer.GetCachedRefField(id))
+                  .filter(f => f)
+                  .map(f => f as Doc);
+        const changesMade = this.props.Document.dockingConfig !== json;
+        if (changesMade) {
+            this.props.Document.dockingConfig = json;
+            this.props.Document.data = new List<Doc>(docs);
+        }
+        this._flush?.end();
+        this._flush = undefined;
+    };
+
     @action
     onPointerUp = (e: MouseEvent): void => {
         window.removeEventListener('pointerup', this.onPointerUp);
-        const flush = this._flush;
-        this._flush = undefined;
-        if (flush) {
-            DragManager.CompleteWindowDrag = undefined;
-            if (!this.stateChanged()) flush.cancel();
-            else flush.end();
-        }
+        DragManager.CompleteWindowDrag = undefined;
+        setTimeout(this.endUndoBatch, 100);
     };
 
     @action
@@ -393,10 +409,8 @@ export class CollectionDockingView extends CollectionSubView() {
             window.addEventListener('mouseup', this.onPointerUp);
             if (!htmlTarget.closest('*.lm_content') && (htmlTarget.closest('*.lm_tab') || htmlTarget.closest('*.lm_stack'))) {
                 const className = typeof htmlTarget.className === 'string' ? htmlTarget.className : '';
-                if (!className.includes('lm_close') && !className.includes('lm_maximise')) {
-                    this._flush = UndoManager.StartBatch('golden layout edit');
-                    DocServer.UPDATE_SERVER_CACHE();
-                }
+                if (className.includes('lm_maximise')) this._flush = UndoManager.StartBatch('tab maximize');
+                else if (!className.includes('lm_close')) DocServer.UPDATE_SERVER_CACHE();
             }
         }
         if (!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
@@ -452,25 +466,12 @@ export class CollectionDockingView extends CollectionSubView() {
     stateChanged = () => {
         this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
         const json = JSON.stringify(this._goldenLayout.toConfig());
-        const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g);
-        const docids = matches?.map(m => m.replace('"documentId":"', '').replace('"', ''));
-        const docs = !docids
-            ? []
-            : docids
-                  .map(id => DocServer.GetCachedRefField(id))
-                  .filter(f => f)
-                  .map(f => f as Doc);
         const changesMade = this.props.Document.dockingConfig !== json;
-        if (changesMade && !this._flush) {
-            UndoManager.RunInBatch(() => {
-                this.props.Document.dockingConfig = json;
-                this.props.Document.data = new List<Doc>(docs);
-            }, 'state changed');
-        }
         return changesMade;
     };
 
     tabDestroyed = (tab: any) => {
+        this._flush = this._flush ?? UndoManager.StartBatch('tab movement');
         if (tab.DashDoc && ![DocumentType.KVP, DocumentType.PRES].includes(tab.DashDoc?.type)) {
             Doc.AddDocToList(Doc.MyHeaderBar, 'data', tab.DashDoc);
             Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', tab.DashDoc, undefined, true, true);
diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
index 63becac1e..2f28ecd00 100644
--- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
@@ -121,7 +121,8 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
 
     @action pointerEntered = () => SnappingManager.GetIsDragging() && (this._background = '#b4b4b4');
     @action pointerLeave = () => (this._background = 'inherit');
-    textCallback = (char: string) => this.addNewTextDoc('-typed text-', false, true);
+    @undoBatch
+    addTextNote = (char: string) => this.addNewTextDoc('-typed text-', false, true);
 
     // addNewTextDoc is called when a user starts typing in a column to create a new node
     @action
@@ -272,7 +273,7 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
                         {!this.props.chromeHidden ? (
                             <div className="collectionNoteTakingView-DocumentButtons" style={{ marginBottom: 10 }}>
                                 <div key={`${heading}-add-document`} className="collectionNoteTakingView-addDocumentButton">
-                                    <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} textCallback={this.textCallback} placeholder={"Type ':' for commands"} contents={'+ New Node'} menuCallback={this.menuCallback} />
+                                    <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} textCallback={this.addTextNote} placeholder={"Type ':' for commands"} contents={'+ New Node'} menuCallback={this.menuCallback} />
                                 </div>
                                 <div key={`${this.props.Document[Id]}-addGroup`} className="collectionNoteTakingView-addDocumentButton">
                                     <EditableView {...this.props.editableViewProps()} />
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 6b4c8a3e9..b131d38d8 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -119,7 +119,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
         // onClick play scripts
         CollectionStackedTimeline.RangeScript =
             CollectionStackedTimeline.RangeScript ||
-            ScriptField.MakeFunction(`scriptContext.clickAnchor(this, clientX)`, {
+            ScriptField.MakeFunction(`setTimeout(() => scriptContext.clickAnchor(this, clientX))`, {
+                // setTimeout is a hack to run script in its own properly named undo group (instead of being part of the generic onClick)
                 self: Doc.name,
                 scriptContext: 'any',
                 clientX: 'number',
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 243550c0b..6be9cb72d 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -128,7 +128,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
 
     @action pointerEntered = () => SnappingManager.GetIsDragging() && (this._background = '#b4b4b4');
     @action pointerLeave = () => (this._background = 'inherit');
-    textCallback = (char: string) => this.addNewTextDoc('-typed text-', false, true);
+    @undoBatch typedNote = (char: string) => this.addNewTextDoc('-typed text-', false, true);
 
     @action
     addNewTextDoc = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => {
@@ -363,7 +363,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
                                 <EditableView
                                     GetValue={returnEmptyString}
                                     SetValue={this.addNewTextDoc}
-                                    textCallback={this.textCallback}
+                                    textCallback={this.typedNote}
                                     placeholder={"Type ':' for commands"}
                                     contents={<FontAwesomeIcon icon={'plus'} />}
                                     menuCallback={this.menuCallback}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index bcda13c8b..cfe78afa1 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -5,7 +5,6 @@ import { AclPrivate, Doc, DocListCast, Field, Opt, StrListCast } from '../../../
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
-import { ScriptField } from '../../../fields/ScriptField';
 import { Cast, ScriptCast, StrCast } from '../../../fields/Types';
 import { WebField } from '../../../fields/URLField';
 import { GestureUtils } from '../../../pen-gestures/GestureUtils';
@@ -65,11 +64,7 @@ export function CollectionSubView<X>(moreProps?: X) {
         // to its children which may be templates.
         // If 'annotationField' is specified, then all children exist on that field of the extension document, otherwise, they exist directly on the data document under 'fieldKey'
         @computed get dataField() {
-            if (this.layoutDoc[this.props.fieldKey]) return this.layoutDoc[this.props.fieldKey];
-            // sets the dataDoc's data field to an empty list if the data field is undefined - prevents issues with addonly
-            // setTimeout changes it outside of the @computed section
-            !this.dataDoc[this.props.fieldKey] && setTimeout(() => !this.dataDoc[this.props.fieldKey] && (this.dataDoc[this.props.fieldKey] = new List<Doc>()));
-            return this.dataDoc[this.props.fieldKey];
+            return this.layoutDoc[this.props.fieldKey];
         }
 
         get childLayoutPairs(): { layout: Doc; data: Doc }[] {
@@ -130,8 +125,9 @@ export function CollectionSubView<X>(moreProps?: X) {
                     const fieldKey = Doc.LayoutFieldKey(d);
                     const annos = !Field.toString(Doc.LayoutField(d) as Field).includes(CollectionView.name);
                     const data = d[annos ? fieldKey + '_annotations' : fieldKey];
-                    if (data !== undefined) {
-                        let subDocs = DocListCast(data);
+                    const side = annos && d[fieldKey + '_sidebar'];
+                    if (data !== undefined || side !== undefined) {
+                        let subDocs = [...DocListCast(data), ...DocListCast(side)];
                         if (subDocs.length > 0) {
                             let newarray: Doc[] = [];
                             notFiltered = notFiltered || (!searchDocs.length && DocUtils.FilterDocs(subDocs, childDocFilters, docRangeFilters, d).length);
@@ -142,6 +138,7 @@ export function CollectionSubView<X>(moreProps?: X) {
                                     const annos = !Field.toString(Doc.LayoutField(t) as Field).includes(CollectionView.name);
                                     notFiltered = notFiltered || ((!searchDocs.length || searchDocs.includes(t)) && ((!childDocFilters.length && !docRangeFilters.length) || DocUtils.FilterDocs([t], childDocFilters, docRangeFilters, d).length));
                                     DocListCast(t[annos ? fieldKey + '_annotations' : fieldKey]).forEach(newdoc => newarray.push(newdoc));
+                                    annos && DocListCast(t[fieldKey + '_sidebar']).forEach(newdoc => newarray.push(newdoc));
                                 });
                                 subDocs = newarray;
                             }
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 7e88959a4..33fa434e1 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -13,14 +13,13 @@ import { listSpec } from '../../../fields/Schema';
 import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../fields/Types';
 import { emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick, Utils } from '../../../Utils';
 import { DocServer } from '../../DocServer';
-import { DocUtils } from '../../documents/Documents';
 import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
 import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager, dropActionType } from '../../util/DragManager';
 import { SelectionManager } from '../../util/SelectionManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
-import { undoBatch, UndoManager } from '../../util/UndoManager';
+import { undoable, UndoManager } from '../../util/UndoManager';
 import { DashboardView } from '../DashboardView';
 import { Colors, Shadows } from '../global/globalEnums';
 import { LightboxView } from '../LightboxView';
@@ -116,15 +115,13 @@ export class TabDocView extends React.Component<TabDocViewProps> {
 
             titleEle.size = StrCast(doc.title).length + 3;
             titleEle.value = doc.title;
-            titleEle.onkeydown = (e: KeyboardEvent) => {
-                e.stopPropagation();
-            };
-            titleEle.onchange = undoBatch(
-                action((e: any) => {
+            titleEle.onkeydown = (e: KeyboardEvent) => e.stopPropagation();
+            titleEle.onchange = (e: any) => {
+                undoable(() => {
                     titleEle.size = e.currentTarget.value.length + 3;
                     Doc.GetProto(doc).title = e.currentTarget.value;
-                })
-            );
+                }, 'edit tab title')();
+            };
 
             if (tab.element[0].children[1].children.length === 1) {
                 iconWrap.className = 'lm_iconWrap lm_moreInfo';
@@ -198,7 +195,9 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                 action(selected => {
                     if (selected) this._activated = true;
                     const toggle = tab.element[0].children[2].children[0] as HTMLInputElement;
-                    selected && tab.contentItem !== tab.header.parent.getActiveContentItem() && UndoManager.RunInBatch(() => tab.header.parent.setActiveContentItem(tab.contentItem), 'tab switch');
+                    if (selected && tab.contentItem !== tab.header.parent.getActiveContentItem()) {
+                        undoable(() => tab.header.parent.setActiveContentItem(tab.contentItem), 'tab switch')();
+                    }
                     toggle.style.fontWeight = selected ? 'bold' : '';
                     // toggle.style.textTransform = selected ? "uppercase" : "";
                 }),
@@ -234,7 +233,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
     public static PinDoc(docs: Doc | Doc[], pinProps: PinProps) {
         const docList = docs instanceof Doc ? [docs] : docs;
 
-        const batch = UndoManager.StartBatch('pinning doc');
+        const batch = UndoManager.StartBatch('Pin doc to pres trail');
         const curPres = Doc.ActivePresentation ?? Doc.MakeCopy(Doc.UserDoc().emptyTrail as Doc, true);
 
         if (!Doc.ActivePresentation) {
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 037148bb9..f56eaee07 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -102,7 +102,7 @@ export class TreeView extends React.Component<TreeViewProps> {
     private _treedropDisposer?: DragManager.DragDropDisposer;
 
     get treeViewOpenIsTransient() {
-        return this.props.treeView.doc.treeViewOpenIsTransient || Doc.IsDocDataProto(this.doc);
+        return this.props.treeView.doc.treeViewOpenIsTransient || Doc.IsDataProto(this.doc);
     }
     set treeViewOpen(c: boolean) {
         if (this.treeViewOpenIsTransient) this._transientOpenState = c;
@@ -221,7 +221,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         } else {
             // choose an appropriate embedding or make one. --- choose the first embedding that (1) user owns,  (2) has no context field ... otherwise make a new embedding
             const bestEmbedding =
-                docView.props.Document.author === Doc.CurrentUserEmail && !Doc.IsDocDataProto(docView.props.Document)
+                docView.props.Document.author === Doc.CurrentUserEmail && !Doc.IsDataProto(docView.props.Document)
                     ? docView.props.Document
                     : DocListCast(this.props.document.proto_embeddings).find(doc => !doc.embedContainer && doc.author === Doc.CurrentUserEmail);
             const nextBestEmbedding = DocListCast(this.props.document.proto_embeddings).find(doc => doc.author === Doc.CurrentUserEmail);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 5ac444147..95046661e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -483,7 +483,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         return false;
     }
 
-    @undoBatch
     @action
     updateClusters(_freeform_useClusters: boolean) {
         this.props.Document._freeform_useClusters = _freeform_useClusters;
@@ -534,7 +533,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         }
     }
 
-    @undoBatch
     @action
     updateCluster(doc: Doc) {
         const childLayouts = this.childLayoutPairs.map(pair => pair.layout);
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 47d7801e6..641088675 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -102,7 +102,6 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         this._lassoPts = [];
     };
 
-    @undoBatch
     @action
     onKeyPress = (e: KeyboardEvent) => {
         //make textbox and add it to this collection
@@ -111,7 +110,6 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         const [x, y] = this.Transform.transformPoint(this._downX, this._downY);
         if (e.key === '?') {
             cm.setDefaultItem('?', (str: string) => this.props.addDocTab(Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, { _width: 400, x, y, _height: 512, _nativeWidth: 850, title: 'bing', data_useCors: true }), OpenWhere.addRight));
-
             cm.displayMenu(this._downX, this._downY, undefined, true);
             e.stopPropagation();
         } else if (e.key === 'u' && this.props.ungroup) {
@@ -173,7 +171,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             e.stopPropagation();
         } else if (!e.ctrlKey && !e.metaKey && SelectionManager.Views().length < 2) {
             FormattedTextBox.SelectOnLoadChar = Doc.UserDoc().defaultTextLayout && !this.props.childLayoutString ? e.key : '';
-            FormattedTextBox.LiveTextUndo = UndoManager.StartBatch('live text batch');
+            FormattedTextBox.LiveTextUndo = UndoManager.StartBatch('type new note');
             this.props.addLiveTextDocument(DocUtils.GetNewTextDoc('-typed text-', x, y, 200, 100, this.props.xPadding === 0));
             e.stopPropagation();
         }
@@ -396,6 +394,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         return newCollection;
     });
 
+    @undoBatch
     @action
     pileup = (e: KeyboardEvent | React.PointerEvent | undefined) => {
         const selected = this.marqueeSelect(false);
@@ -508,8 +507,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
     };
 
     @undoBatch
-    @action
-    summary = (e: KeyboardEvent | React.PointerEvent | undefined) => {
+    summary = action((e: KeyboardEvent | React.PointerEvent | undefined) => {
         const selected = this.marqueeSelect(false).map(d => {
             this.props.removeDocument?.(d);
             d.x = NumCast(d.x) - this.Bounds.left;
@@ -534,19 +532,10 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         this.props.addDocument?.(portal);
         this.props.addLiveTextDocument(summary);
         MarqueeOptionsMenu.Instance.fadeOut(true);
-    };
+    });
 
     @action
-    background = (e: KeyboardEvent | React.PointerEvent | undefined) => {
-        const newCollection = this.getCollection([], undefined, undefined);
-        this.props.addDocument?.(newCollection);
-        MarqueeOptionsMenu.Instance.fadeOut(true);
-        this.hideMarquee();
-        setTimeout(() => this.props.selectDocuments([newCollection]));
-    };
-
-    @undoBatch
-    marqueeCommand = action((e: KeyboardEvent) => {
+    marqueeCommand = (e: KeyboardEvent) => {
         if (this._commandExecuted || (e as any).propagationIsStopped) {
             return;
         }
@@ -557,7 +546,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             this.delete(e, e.key === 'h');
             e.stopPropagation();
         }
-        if ('cbtsSpg'.indexOf(e.key) !== -1) {
+        if ('ctsSpg'.indexOf(e.key) !== -1) {
             this._commandExecuted = true;
             e.stopPropagation();
             e.preventDefault();
@@ -565,7 +554,6 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             if (e.key === 'g') this.collection(e, true);
             if (e.key === 'c' || e.key === 't') this.collection(e);
             if (e.key === 's' || e.key === 'S') this.summary(e);
-            if (e.key === 'b') this.background(e);
             if (e.key === 'p') this.pileup(e);
             this.cleanupInteractions(false);
         }
@@ -575,7 +563,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             e.preventDefault();
             this._lassoFreehand = !this._lassoFreehand;
         }
-    });
+    };
 
     touchesLine(r1: { left: number; top: number; width: number; height: number }) {
         for (const lassoPt of this._lassoPts) {
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index bcc2ca129..2a68f9b58 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -13,7 +13,7 @@ import { Docs, DocumentOptions, DocUtils, FInfo } from '../../../documents/Docum
 import { DocumentManager } from '../../../util/DocumentManager';
 import { DragManager } from '../../../util/DragManager';
 import { SelectionManager } from '../../../util/SelectionManager';
-import { undoBatch } from '../../../util/UndoManager';
+import { undoable, undoBatch } from '../../../util/UndoManager';
 import { ContextMenu } from '../../ContextMenu';
 import { EditableView } from '../../EditableView';
 import { Colors } from '../../global/globalEnums';
@@ -25,6 +25,8 @@ import { CollectionSubView } from '../CollectionSubView';
 import './CollectionSchemaView.scss';
 import { SchemaColumnHeader } from './SchemaColumnHeader';
 import { SchemaRowBox } from './SchemaRowBox';
+import { faHeartPulse } from '@fortawesome/free-solid-svg-icons';
+import { faGalacticSenate } from '@fortawesome/free-brands-svg-icons';
 
 export enum ColumnType {
     Number,
@@ -224,7 +226,7 @@ export class CollectionSchemaView extends CollectionSubView() {
 
     @undoBatch
     @action
-    setSort = (field: string | undefined, desc: boolean = false) => {
+    setColumnSort = (field: string | undefined, desc: boolean = false) => {
         this.layoutDoc.sortField = field;
         this.layoutDoc.sortDesc = desc;
     };
@@ -484,24 +486,11 @@ export class CollectionSchemaView extends CollectionSubView() {
         return false;
     };
 
-    @action
-    addNewTextDoc = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => {
-        if (!value && !forceEmptyNote) return false;
-        const newDoc = Docs.Create.TextDocument(value, { title: value, _layout_autoHeight: true });
-        FormattedTextBox.SelectOnLoad = newDoc[Id];
-        FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? '' : ' ';
-        return this.addRow(newDoc) || false;
-    };
-
     menuCallback = (x: number, y: number) => {
         ContextMenu.Instance.clearItems();
 
-        DocUtils.addDocumentCreatorMenuItems(doc => this.addRow(doc), this.addRow, x, y, true);
+        DocUtils.addDocumentCreatorMenuItems(this.addRow, this.addRow, x, y, true);
 
-        ContextMenu.Instance.setDefaultItem('::', (name: string): void => {
-            Doc.GetProto(this.props.Document)[name] = '';
-            this.addRow(Docs.Create.TextDocument('', { title: name, _layout_autoHeight: true }));
-        });
         ContextMenu.Instance.displayMenu(x, y, undefined, true);
     };
 
@@ -866,7 +855,7 @@ export class CollectionSchemaView extends CollectionSubView() {
                                 columnWidths={this.displayColumnWidths}
                                 sortField={this.sortField}
                                 sortDesc={this.sortDesc}
-                                setSort={this.setSort}
+                                setSort={this.setColumnSort}
                                 rowHeight={this.rowHeightFunc}
                                 removeColumn={this.removeColumn}
                                 resizeColumn={this.startResize}
@@ -879,7 +868,14 @@ export class CollectionSchemaView extends CollectionSubView() {
                     {this._columnMenuIndex !== undefined && this.renderColumnMenu}
                     {this._filterColumnIndex !== undefined && this.renderFilterMenu}
                     <CollectionSchemaViewDocs schema={this} childDocs={this.sortedDocsFunc} rowHeight={this.rowHeightFunc} setRef={(ref: HTMLDivElement | null) => (this._tableContentRef = ref)} />
-                    <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} placeholder={"Type ':' for commands"} contents={'+ New Node'} menuCallback={this.menuCallback} height={CollectionSchemaView._newNodeInputHeight} />
+                    <EditableView
+                        GetValue={returnEmptyString}
+                        SetValue={undoable(value => (value ? this.addRow(Docs.Create.TextDocument(value, { title: value, _layout_autoHeight: true })) : false), 'add text doc')}
+                        placeholder={"Type ':' for commands"}
+                        contents={'+ New Node'}
+                        menuCallback={this.menuCallback}
+                        height={CollectionSchemaView._newNodeInputHeight}
+                    />
                 </div>
                 {this.previewWidth > 0 && <div className="schema-preview-divider" style={{ width: CollectionSchemaView._previewDividerWidth }} onPointerDown={this.onDividerDown}></div>}
                 {this.previewWidth > 0 && (
diff --git a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
index 7da3c042c..46c2f622b 100644
--- a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
@@ -5,8 +5,6 @@ import { observer } from 'mobx-react';
 import { emptyFunction, setupMoveUpEvents } from '../../../../Utils';
 import { Colors } from '../../global/globalEnums';
 import './CollectionSchemaView.scss';
-import { SnappingManager } from '../../../util/SnappingManager';
-import { DragManager } from '../../../util/DragManager';
 
 export interface SchemaColumnHeaderProps {
     columnKeys: string[];
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index 45bfe4f77..978b65053 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -7,7 +7,7 @@ import { Doc } from '../../../../fields/Doc';
 import { BoolCast } from '../../../../fields/Types';
 import { DragManager } from '../../../util/DragManager';
 import { SnappingManager } from '../../../util/SnappingManager';
-import { undoBatch } from '../../../util/UndoManager';
+import { undoable, undoBatch } from '../../../util/UndoManager';
 import { ViewBoxBaseComponent } from '../../DocComponent';
 import { Colors } from '../../global/globalEnums';
 import { OpenWhere } from '../../nodes/DocumentView';
@@ -111,18 +111,18 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     }}>
                     <div
                         className="schema-row-button"
-                        onPointerDown={undoBatch(e => {
+                        onPointerDown={undoable(e => {
                             e.stopPropagation();
                             this.props.removeDocument?.(this.rootDoc);
-                        })}>
+                        }, 'Delete Row')}>
                         <FontAwesomeIcon icon="times" />
                     </div>
                     <div
                         className="schema-row-button"
-                        onPointerDown={e => {
+                        onPointerDown={undoable(e => {
                             e.stopPropagation();
                             this.props.addDocTab(this.rootDoc, OpenWhere.addRight);
-                        }}>
+                        }, 'Open Doc on Right')}>
                         <FontAwesomeIcon icon="external-link-alt" />
                     </div>
                 </div>
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 42bf32475..6d5ef9df6 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -122,7 +122,7 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
                         const ret = KeyValueBox.SetField(this.props.Document, this.props.fieldKey.replace(/^_/, ''), value);
                         this.props.finishEdit?.();
                         return ret;
-                    })}
+                    }, 'edit schema cell')}
                 />
             </div>
         );
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index 3f6369898..65d13a6c3 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -49,7 +49,7 @@ export class LinkMenu extends React.Component<Props> {
             <LinkMenuGroup key={group[0]} itemHandler={this.props.itemHandler} docView={this.props.docView} sourceDoc={this.props.docView.props.Document} group={group[1]} groupType={group[0]} clearLinkEditor={this.clear} />
         ));
 
-        return linkItems.length ? linkItems : this.props.style ? [<></>] : [<p key="">No links have been created yet. Drag the linking button onto another document to create a link.</p>];
+        return linkItems.length ? linkItems : this.props.style ? [] : [<p key="none">No links have been created yet. Drag the linking button onto another document to create a link.</p>];
     };
 
     render() {
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 8e83cf121..410e0bbdc 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -447,7 +447,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
 
     timelineScreenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -AudioBox.topControlsHeight);
 
-    setPlayheadTime = (time: number) => (this._ele!.currentTime = this.layoutDoc._layout_currentTimecode = time);
+    setPlayheadTime = (time: number) => (this._ele!.currentTime /*= this.layoutDoc._layout_currentTimecode*/ = time);
 
     playing = () => this.mediaState === media_state.Playing;
 
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index d5ca30957..bd1952ecb 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -9,7 +9,7 @@ import { DocUtils } from '../../documents/Documents';
 import { DragManager } from '../../util/DragManager';
 import { Hypothesis } from '../../util/HypothesisUtils';
 import { LinkManager } from '../../util/LinkManager';
-import { undoBatch, UndoManager } from '../../util/UndoManager';
+import { undoable, undoBatch, UndoManager } from '../../util/UndoManager';
 import './DocumentLinksButton.scss';
 import { DocumentView } from './DocumentView';
 import { LinkDescriptionPopup } from './LinkDescriptionPopup';
@@ -78,7 +78,6 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
         );
     };
 
-    @undoBatch
     onLinkButtonDown = (e: React.PointerEvent): void => {
         setupMoveUpEvents(
             this,
@@ -123,17 +122,14 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
             e,
             returnFalse,
             emptyFunction,
-            undoBatch(
-                action(e => {
-                    DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.props.View.props.Document, true, this.props.View);
-                })
-            )
+            action(e => DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.props.View.props.Document, true, this.props.View))
         );
     };
 
-    public static finishLinkClick = undoBatch(
-        action((screenX: number, screenY: number, startLink: Doc, endLink: Doc, startIsAnnotation: boolean, endLinkView?: DocumentView, pinProps?: PinProps) => {
-            if (startLink === endLink) {
+    @undoBatch
+    public static finishLinkClick(screenX: number, screenY: number, startLink: Doc | undefined, endLink: Doc, startIsAnnotation: boolean, endLinkView?: DocumentView, pinProps?: PinProps) {
+        runInAction(() => {
+            if (startLink === endLink || !startLink) {
                 DocumentLinksButton.StartLink = undefined;
                 DocumentLinksButton.StartLinkView = undefined;
                 DocumentLinksButton.AnnotationId = undefined;
@@ -185,8 +181,8 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
                     );
                 }
             }
-        })
-    );
+        });
+    }
 
     @action clearLinks() {
         DocumentLinksButton.StartLink = undefined;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 359b72352..52eee84ac 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -7,6 +7,7 @@ import { AclPrivate, AnimationSym, DataSym, Doc, DocListCast, Field, Opt, StrLis
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
+import { RefField } from '../../../fields/RefField';
 import { listSpec } from '../../../fields/Schema';
 import { ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
@@ -35,8 +36,11 @@ import { ContextMenuProps } from '../ContextMenuItem';
 import { DocComponent } from '../DocComponent';
 import { EditableView } from '../EditableView';
 import { GestureOverlay } from '../GestureOverlay';
+import { InkingStroke } from '../InkingStroke';
 import { LightboxView } from '../LightboxView';
+import { OverlayView } from '../OverlayView';
 import { StyleProp } from '../StyleProvider';
+import { UndoStack } from '../UndoStack';
 import { CollectionFreeFormDocumentView } from './CollectionFreeFormDocumentView';
 import { DocumentContentsView, ObserverJsxParser } from './DocumentContentsView';
 import { DocumentLinksButton } from './DocumentLinksButton';
@@ -47,8 +51,6 @@ import { LinkAnchorBox } from './LinkAnchorBox';
 import { PresEffect, PresEffectDirection } from './trails';
 import { PinProps, PresBox } from './trails/PresBox';
 import React = require('react');
-import { InkingStroke } from '../InkingStroke';
-import { RefField } from '../../../fields/RefField';
 const { Howl } = require('howler');
 
 interface Window {
@@ -456,27 +458,26 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                         // instead of in the global lightbox
                         const oldFunc = DocumentViewInternal.addDocTabFunc;
                         DocumentViewInternal.addDocTabFunc = this.props.addDocTab;
-                        const res =
-                            this.onClickHandler?.script.run(
-                                {
-                                    this: this.layoutDoc,
-                                    self: this.rootDoc,
-                                    _readOnly_: false,
-                                    scriptContext: this.props.scriptContext,
-                                    documentView: this.props.DocumentView(),
-                                    clientX,
-                                    clientY,
-                                    shiftKey,
-                                    altKey,
-                                    metaKey,
-                                },
-                                console.log
-                            ).result?.select === true
-                                ? this.props.select(false)
-                                : '';
+                        this.onClickHandler?.script.run(
+                            {
+                                this: this.layoutDoc,
+                                self: this.rootDoc,
+                                _readOnly_: false,
+                                scriptContext: this.props.scriptContext,
+                                documentView: this.props.DocumentView(),
+                                clientX,
+                                clientY,
+                                shiftKey,
+                                altKey,
+                                metaKey,
+                            },
+                            console.log
+                        ).result?.select === true
+                            ? this.props.select(false)
+                            : '';
                         DocumentViewInternal.addDocTabFunc = oldFunc;
                     };
-                    clickFunc = () => (this.props.Document.dontUndo ? func() : UndoManager.RunInBatch(func, 'on click'));
+                    clickFunc = () => (this.props.Document.dontUndo ? func() : UndoManager.RunInBatch(func, 'click ' + this.rootDoc.title));
                 } else {
                     // 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
                     if ((this.layoutDoc.onDragStart || this.props.Document.rootDocument) && !(e.ctrlKey || e.button > 0)) {
@@ -490,7 +491,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 if ((clickFunc && waitFordblclick !== 'never') || waitFordblclick === 'always') {
                     this._doubleClickTimeout && clearTimeout(this._doubleClickTimeout);
                     this._doubleClickTimeout = setTimeout(this._singleClickFunc, 300);
-                } else {
+                } else if (!DocumentView.LongPress) {
                     this._singleClickFunc();
                     this._singleClickFunc = undefined;
                 }
@@ -502,7 +503,15 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
     @action
     onPointerDown = (e: React.PointerEvent): void => {
-        this._longPressSelector = setTimeout(() => DocumentView.LongPress && this.props.select(false), 1000);
+        this._longPressSelector = setTimeout(() => {
+            if (DocumentView.LongPress) {
+                if (this.rootDoc.dontUndo) {
+                    OverlayView.Instance.addWindow(<UndoStack />, { x: 300, y: 100, width: 200, height: 200, title: 'Undo Stack' });
+                } else {
+                    this.props.select(false);
+                }
+            }
+        }, 1000);
         if (!GestureOverlay.DownDocView) GestureOverlay.DownDocView = this.props.DocumentView();
 
         this._downX = e.clientX;
@@ -557,7 +566,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         if (this.onPointerUpHandler?.script) {
             this.onPointerUpHandler.script.run({ self: this.rootDoc, this: this.layoutDoc }, console.log);
         } else if (e.button === 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) {
-            this._doubleTap = Date.now() - this._lastTap < Utils.CLICK_TIME;
+            this._doubleTap = this.rootDoc.defaultDoubleClick !== 'ignore' && Date.now() - this._lastTap < Utils.CLICK_TIME;
             if (!this.props.isSelected(true)) this._lastTap = Date.now(); // don't want to process the start of a double tap if the doucment is selected
         }
         if (DocumentView.LongPress) e.preventDefault();
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index c6172ee01..450176a49 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -123,7 +123,6 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
                                 this._markerTargetDoc = linkTarget;
                                 this._targetDoc = /*linkTarget?.type === DocumentType.MARKER &&*/ linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget;
                             }
-                            this._toolTipText = 'link to ' + this._targetDoc?.title;
                             if (LinkDocPreview.LinkInfo?.noPreview || this._linkSrc?.followLinkToggle || this._markerTargetDoc?.type === DocumentType.PRES) this.followLink();
                         }
                     })
@@ -296,7 +295,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
                 className="linkDocPreview"
                 ref={this._linkDocRef}
                 onPointerDown={this.followLinkPointerDown}
-                style={{ display: !this._toolTipText ? 'none' : undefined, left: this.props.location[0], top: this.props.location[1], width: this.width() + borders, height: this.height() + borders + (this.props.showHeader ? 37 : 0) }}>
+                style={{ left: this.props.location[0], top: this.props.location[1], width: this.width() + borders, height: this.height() + borders + (this.props.showHeader ? 37 : 0) }}>
                 {this.docPreview}
             </div>
         );
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index 57aa852ac..b07cf7e00 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -16,7 +16,7 @@ import { CollectionViewType, DocumentType } from '../../../documents/DocumentTyp
 import { LinkManager } from '../../../util/LinkManager';
 import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
 import { SelectionManager } from '../../../util/SelectionManager';
-import { undoBatch, UndoManager } from '../../../util/UndoManager';
+import { undoable, undoBatch, UndoManager } from '../../../util/UndoManager';
 import { CollectionFreeFormView } from '../../collections/collectionFreeForm';
 import { ContextMenu } from '../../ContextMenu';
 import { DocComponent } from '../../DocComponent';
@@ -129,29 +129,27 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
      * Number button
      */
     @computed get numberSliderButton() {
-        const numScript = ScriptCast(this.rootDoc.script);
-        const setValue = (value: number) => UndoManager.RunInBatch(() => numScript?.script.run({ self: this.rootDoc, value, _readOnly_: false }), 'set num value');
-
+        const numScript = (value?: number) => ScriptCast(this.rootDoc.script).script.run({ self: this.rootDoc, value, _readOnly_: value === undefined });
         // Script for checking the outcome of the toggle
-        const checkResult = Number(numScript?.script.run({ self: this.rootDoc, value: 0, _readOnly_: true }).result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3));
-
+        const checkResult = Number(numScript().result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3));
         const label = !FontIconBox.GetShowLabels() ? null : <div className="fontIconBox-label">{this.label}</div>;
 
         const dropdown = (
             <div className="menuButton-dropdownBox" onPointerDown={e => e.stopPropagation()}>
                 <input
+                    className="menu-slider"
                     type="range"
                     step="1"
                     min={NumCast(this.rootDoc.numBtnMin, 0)}
                     max={NumCast(this.rootDoc.numBtnMax, 100)}
+                    //readOnly={true}
                     value={checkResult}
-                    className="menu-slider"
-                    onPointerDown={() => (this._batch = UndoManager.StartBatch('presDuration'))}
+                    onPointerDown={() => (this._batch = UndoManager.StartBatch('num slider changing'))}
                     onPointerUp={() => this._batch?.end()}
-                    onChange={e => {
+                    onChange={undoable(e => {
                         e.stopPropagation();
-                        setValue(Number(e.target.value));
-                    }}
+                        numScript(Number(e.target.value));
+                    }, 'set num value')}
                 />
             </div>
         );
@@ -174,20 +172,13 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
      * Number button
      */
     @computed get numberDropdownButton() {
-        const numScript = ScriptCast(this.rootDoc.script);
-        const setValue = (value: number) => UndoManager.RunInBatch(() => numScript?.script.run({ self: this.rootDoc, value, _readOnly_: false }), 'set num value');
-
-        // Script for checking the outcome of the toggle
-        const checkResult = Number(numScript?.script.run({ self: this.rootDoc, value: 0, _readOnly_: true }).result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3));
+        const numScript = (value?: number) => ScriptCast(this.rootDoc.script)?.script.run({ self: this.rootDoc, value, _readOnly_: value === undefined });
 
-        const label = !FontIconBox.GetShowLabels() ? null : <div className="fontIconBox-label">{this.label}</div>;
+        const checkResult = Number(numScript().result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3));
 
         const items: number[] = [];
-        for (let i = 0; i < 100; i++) {
-            if (i % 2 === 0) {
-                items.push(i);
-            }
-        }
+        for (let i = 0; i < 100; i += 2) items.push(i);
+
         const list = items.map(value => {
             return (
                 <div
@@ -196,15 +187,15 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                     style={{
                         backgroundColor: value.toString() === checkResult ? Colors.LIGHT_BLUE : undefined,
                     }}
-                    onClick={() => setValue(value)}>
+                    onClick={undoable(value => numScript(value), `${this.rootDoc.title} button set from list`)}>
                     {value}
                 </div>
             );
         });
         return (
             <div className="menuButton numBtn list">
-                <div className="button" onClick={action(e => setValue(Number(checkResult) - 1))}>
-                    <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={'minus'} />
+                <div className="button" onClick={undoable(e => numScript(Number(checkResult) - 1), `${this.rootDoc.title} decrement value`)}>
+                    <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon="minus" />
                 </div>
                 <div
                     className={`button ${'number'}`}
@@ -217,9 +208,9 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                         this.noTooltip = this.rootDoc.dropDownOpen;
                         Doc.UnBrushAllDocs();
                     })}>
-                    <input style={{ width: 30 }} className="button-input" type="number" value={checkResult} onChange={undoBatch(action(e => setValue(Number(e.target.value))))} />
+                    <input style={{ width: 30 }} className="button-input" type="number" value={checkResult} readOnly={true} onChange={undoable(e => numScript(Number(e.target.value)), `${this.rootDoc.title} button set value`)} />
                 </div>
-                <div className={`button`} onClick={action(e => setValue(Number(checkResult) + 1))}>
+                <div className={`button`} onClick={undoable(e => numScript(Number(checkResult) + 1), `${this.rootDoc.title} increment value`)}>
                     <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={'plus'} />
                 </div>
 
@@ -322,12 +313,12 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
             .map(value => (
                 <div
                     className="list-item"
-                    key={`${value}`}
+                    key={value}
                     style={{
                         fontFamily: script.script.originalScript.startsWith('{ return setFont') ? value : undefined,
                         backgroundColor: value === text ? Colors.LIGHT_BLUE : undefined,
                     }}
-                    onClick={undoBatch(() => script.script.run({ self: this.rootDoc, value }))}>
+                    onClick={undoable(() => script.script.run({ self: this.rootDoc, value }), value)}>
                     {value[0].toUpperCase() + value.slice(1)}
                 </div>
             ));
@@ -640,29 +631,26 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
 ScriptingGlobals.add(function showFreeform(attr: 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll', checkResult?: boolean) {
     const selected = SelectionManager.Docs().lastElement();
     // prettier-ignore
-    const map: Map<'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll', { undo: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc) => void;}> = new Map([
+    const map: Map<'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc) => void;}> = new Map([
         ['grid', {
-            undo: false,
             checkResult: (doc:Doc) => doc._freeform_backgroundGrid,
             setDoc: (doc:Doc) => doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid,
         }],
         ['snaplines', {
-            undo: false,
             checkResult: (doc:Doc) => doc._freeform_snapLines,
             setDoc: (doc:Doc) => doc._freeform_snapLines = !doc._freeform_snapLines,
         }],
         ['viewAll', {
-            undo: false,
             checkResult: (doc:Doc) => doc._freeform_fitContentsToBox,
             setDoc: (doc:Doc) => doc._freeform_fitContentsToBox = !doc._freeform_fitContentsToBox,
         }],
         ['clusters', {
-            undo: false,
+            waitForRender: true, // flags that undo batch should terminate after a re-render giving the script the chance to fire
             checkResult: (doc:Doc) => doc._freeform_useClusters,
             setDoc: (doc:Doc) => doc._freeform_useClusters = !doc._freeform_useClusters,
         }],
         ['arrange', {
-            undo: true,
+            waitForRender: true, // flags that undo batch should terminate after a re-render giving the script the chance to fire
             checkResult: (doc:Doc) => doc._autoArrange,
             setDoc: (doc:Doc) => doc._autoArrange = !doc._autoArrange,
         }],
@@ -671,7 +659,7 @@ ScriptingGlobals.add(function showFreeform(attr: 'grid' | 'snaplines' | 'cluster
     if (checkResult) {
         return map.get(attr)?.checkResult(selected) ? Colors.MEDIUM_BLUE : 'transparent';
     }
-    const batch = map.get(attr)?.undo ? UndoManager.StartBatch('set feature') : { end: () => {} };
+    const batch = map.get(attr)?.waitForRender ? UndoManager.StartBatch('set freeform attribute') : { end: () => {} };
     SelectionManager.Docs().map(dv => map.get(attr)?.setDoc(dv));
     setTimeout(() => batch.end(), 100);
 });
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 9c06aa7d8..e85835002 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -301,11 +301,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             this._editorView.updateState(state);
 
             const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc.proto), this.fieldKey) ? DocCast(this.layoutDoc.proto) : this.dataDoc;
-            const curText = state.doc.textBetween(0, state.doc.content.size, ' \n');
-            const curTemp = this.layoutDoc.resolvedDataDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField) : undefined; // the actual text in the text box
-            const curProto = Cast(Cast(dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype
-            const curLayout = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template
-            const json = JSON.stringify(state.toJSON());
+            const newText = state.doc.textBetween(0, state.doc.content.size, ' \n');
+            const newJson = JSON.stringify(state.toJSON());
+            const prevData = Cast(this.layoutDoc[this.fieldKey], RichTextField, null); // the actual text in the text box
+            const prevLayoutData = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template
+            const protoData = Cast(Cast(dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype
             const effectiveAcl = GetEffectiveAcl(dataDoc);
 
             const removeSelection = (json: string | undefined) => (json?.indexOf('"storedMarks"') === -1 ? json?.replace(/"selection":.*/, '') : json?.replace(/"selection":"\"storedMarks\""/, '"storedMarks"'));
@@ -320,29 +320,25 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 dataDoc.tags = accumTags.length ? new List<string>(Array.from(new Set<string>(accumTags))) : undefined;
 
                 let unchanged = true;
-                if (this._applyingChange !== this.fieldKey && removeSelection(json) !== removeSelection(curProto?.Data)) {
+                if (this._applyingChange !== this.fieldKey && removeSelection(newJson) !== removeSelection(prevData?.Data)) {
                     this._applyingChange = this.fieldKey;
-                    const textChange = curText !== Cast(dataDoc[this.fieldKey], RichTextField)?.Text;
+                    const textChange = newText !== prevData?.Text;
                     textChange && (dataDoc[this.fieldKey + '_modificationDate'] = new DateField(new Date(Date.now())));
-                    if ((!curTemp && !curProto) || curText || json.includes('dash')) {
+                    if ((!prevData && !protoData) || newText || (!newText && !protoData)) {
                         // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
-                        if (removeSelection(json) !== removeSelection(curLayout?.Data)) {
+                        if (removeSelection(newJson) !== removeSelection(prevLayoutData?.Data)) {
                             const numstring = NumCast(dataDoc[this.fieldKey], null);
-                            if (numstring !== undefined) {
-                                dataDoc[this.fieldKey] = Number(curText);
-                            } else {
-                                dataDoc[this.fieldKey] = new RichTextField(json, curText);
-                            }
+                            dataDoc[this.fieldKey] = numstring !== undefined ? Number(newText) : new RichTextField(newJson, newText);
                             dataDoc[this.fieldKey + '_noTemplate'] = true; //(curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited
-                            textChange && ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: curText });
+                            textChange && ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: newText });
                             unchanged = false;
                         }
                     } else {
                         // if we've deleted all the text in a note driven by a template, then restore the template data
                         dataDoc[this.fieldKey] = undefined;
-                        this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse((curProto || curTemp).Data)));
+                        this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse((protoData || prevData).Data)));
                         dataDoc[this.fieldKey + '_noTemplate'] = undefined; // mark the data field as not being split from any template it might have
-                        ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: curText });
+                        ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: newText });
                         unchanged = false;
                     }
                     this._applyingChange = '';
@@ -667,7 +663,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         this.layoutDoc._width = !preview && this.SidebarShown ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) * prevWidth);
     };
     sidebarDown = (e: React.PointerEvent) => {
-        const batch = UndoManager.StartBatch('sidebar');
+        const batch = UndoManager.StartBatch('toggle sidebar');
         setupMoveUpEvents(
             this,
             e,
@@ -694,13 +690,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         return false;
     };
 
-    @undoBatch
     deleteAnnotation = (anchor: Doc) => {
+        const batch = UndoManager.StartBatch('delete link');
         LinkManager.Instance.deleteLink(LinkManager.Links(anchor)[0]);
         // const docAnnotations = DocListCast(this.props.dataDoc[this.fieldKey]);
         // this.props.dataDoc[this.fieldKey] = new List<Doc>(docAnnotations.filter(a => a !== this.annoTextRegion));
         // AnchorMenu.Instance.fadeOut(true);
         this.props.select(false);
+        setTimeout(batch.end); // wait for reaction to remove link from document
     };
 
     @undoBatch
@@ -733,17 +730,22 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 .split(' ')
                 .filter(h => h);
             const anchorDoc = Array.from(hrefs).lastElement().replace(Doc.localServerPath(), '').split('?')[0];
+            const deleteMarkups = undoBatch(() => {
+                const sel = editor.state.selection;
+                editor.dispatch(editor.state.tr.removeMark(sel.from, sel.to, editor.state.schema.marks.linkAnchor));
+            });
             e.persist();
             anchorDoc &&
                 DocServer.GetRefField(anchorDoc).then(
                     action(anchor => {
+                        anchor && SelectionManager.SelectSchemaViewDoc(anchor as Doc);
                         AnchorMenu.Instance.Status = 'annotation';
-                        AnchorMenu.Instance.Delete = () => this.deleteAnnotation(anchor as Doc);
+                        AnchorMenu.Instance.Delete = !anchor && editor.state.selection.empty ? returnFalse : !anchor ? deleteMarkups : () => this.deleteAnnotation(anchor as Doc);
                         AnchorMenu.Instance.Pinned = false;
-                        AnchorMenu.Instance.PinToPres = () => this.pinToPres(anchor as Doc);
-                        AnchorMenu.Instance.MakeTargetToggle = () => this.makeTargetToggle(anchor as Doc);
-                        AnchorMenu.Instance.ShowTargetTrail = () => this.showTargetTrail(anchor as Doc);
-                        AnchorMenu.Instance.IsTargetToggler = () => this.isTargetToggler(anchor as Doc);
+                        AnchorMenu.Instance.PinToPres = !anchor ? returnFalse : () => this.pinToPres(anchor as Doc);
+                        AnchorMenu.Instance.MakeTargetToggle = !anchor ? returnFalse : () => this.makeTargetToggle(anchor as Doc);
+                        AnchorMenu.Instance.ShowTargetTrail = !anchor ? returnFalse : () => this.showTargetTrail(anchor as Doc);
+                        AnchorMenu.Instance.IsTargetToggler = !anchor ? returnFalse : () => this.isTargetToggler(anchor as Doc);
                         AnchorMenu.Instance.jumpTo(e.clientX, e.clientY, true);
                     })
                 );
@@ -1002,7 +1004,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     anchorDoc ??
                     Docs.Create.TextConfigDocument({
                         //
-                        title: '#' + this._editorView?.state.doc.textBetween(sel.from, sel.to),
+                        title: 'text(' + this._editorView?.state.doc.textBetween(sel.from, sel.to) + ')',
                         annotationOn: this.dataDoc,
                     });
                 const href = targetHref ?? Doc.localServerPath(anchor);
@@ -1480,11 +1482,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         }
 
         const selectOnLoad = this.rootDoc[Id] === FormattedTextBox.SelectOnLoad && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath()));
-        if (selectOnLoad && !this.props.dontRegisterView && !this.props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) {
+        if (this._editorView && selectOnLoad && !this.props.dontRegisterView && !this.props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) {
             const selLoadChar = FormattedTextBox.SelectOnLoadChar;
             FormattedTextBox.SelectOnLoad = '';
             this.props.select(false);
-            if (selLoadChar && this._editorView) {
+            if (selLoadChar) {
                 const $from = this._editorView.state.selection.anchor ? this._editorView.state.doc.resolve(this._editorView.state.selection.anchor - 1) : undefined;
                 const mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) });
                 const curMarks = this._editorView.state.storedMarks ?? $from?.marksAcross(this._editorView.state.selection.$head) ?? [];
@@ -1494,10 +1496,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     .insertText(FormattedTextBox.SelectOnLoadChar, this._editorView.state.doc.content.size - 1, this._editorView.state.doc.content.size)
                     .setStoredMarks(storedMarks);
                 this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size))));
-            } else if (this._editorView && curText && !FormattedTextBox.DontSelectInitialText) {
+            } else if (curText && !FormattedTextBox.DontSelectInitialText) {
                 selectAll(this._editorView.state, this._editorView?.dispatch);
-                this.startUndoTypingBatch();
-            } else if (this._editorView) {
+            } else {
                 this._editorView.dispatch(this._editorView.state.tr.addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })));
             }
         }
@@ -1506,7 +1507,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         if (this._editorView) {
             const tr = this._editorView.state.tr;
             const { from, to } = tr.selection;
-            // for some reason, the selection is sometimes lost in the sidebar view when prosemirror syncs the seledtion with the dom, so reset the selectoin after the document has ben fully instantiated.
+            // for some reason, the selection is sometimes lost in the sidebar view when prosemirror syncs the seledtion with the dom, so reset the selection after the document has ben fully instantiated.
             if (FormattedTextBox.DontSelectInitialText) setTimeout(() => this._editorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(from), tr.doc.resolve(to)))), 250);
             this._editorView.state.storedMarks = [
                 ...(this._editorView.state.storedMarks ?? []),
@@ -1630,7 +1631,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     onFocused = (e: React.FocusEvent): void => {
         //applyDevTools.applyDevTools(this._editorView);
         this.ProseRef?.children[0] === e.nativeEvent.target && this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props);
-        this.startUndoTypingBatch();
+        e.stopPropagation();
     };
 
     onClick = (e: React.MouseEvent): void => {
@@ -1705,13 +1706,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         }
     }
     startUndoTypingBatch() {
-        !this._undoTyping && (this._undoTyping = UndoManager.StartBatch('undoTyping'));
+        !this._undoTyping && (this._undoTyping = UndoManager.StartBatch('text edits on ' + this.rootDoc.title));
     }
     public endUndoTypingBatch() {
-        const wasUndoing = this._undoTyping;
         this._undoTyping?.end();
         this._undoTyping = undefined;
-        return wasUndoing;
     }
 
     @action
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 3d1d11141..3eadc3047 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -1428,6 +1428,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 min={min}
                 max={max}
                 value={value}
+                readOnly={true}
                 style={{ marginLeft: hmargin, marginRight: hmargin, width: `calc(100% - ${2 * (hmargin ?? 0)}px)` }}
                 className={`toolbar-slider ${active ? '' : 'none'}`}
                 onPointerDown={e => {
@@ -1501,7 +1502,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             <div className="ribbon-doubleButton">
                                 <div className="presBox-subheading">Slide Duration</div>
                                 <div className="ribbon-property">
-                                    <input className="presBox-input" type="number" value={duration} onKeyDown={e => e.stopPropagation()} onChange={e => this.updateDurationTime(e.target.value)} /> s
+                                    <input className="presBox-input" type="number" readOnly={true} value={duration} onKeyDown={e => e.stopPropagation()} onChange={e => this.updateDurationTime(e.target.value)} /> s
                                 </div>
                                 <div className="ribbon-propertyUpDown">
                                     <div className="ribbon-propertyUpDownItem" onClick={() => this.updateDurationTime(String(duration), 1000)}>
@@ -1654,7 +1655,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         <div className="ribbon-doubleButton" style={{ display: activeItem.presMovement === PresMovement.Zoom ? 'inline-flex' : 'none' }}>
                             <div className="presBox-subheading">Zoom (% screen filled)</div>
                             <div className="ribbon-property">
-                                <input className="presBox-input" type="number" value={zoom} onChange={e => this.updateZoom(e.target.value)} />%
+                                <input className="presBox-input" type="number" readOnly={true} value={zoom} onChange={e => this.updateZoom(e.target.value)} />%
                             </div>
                             <div className="ribbon-propertyUpDown">
                                 <div className="ribbon-propertyUpDownItem" onClick={() => this.updateZoom(String(zoom), 0.1)}>
@@ -1669,7 +1670,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}>
                             <div className="presBox-subheading">Transition Time</div>
                             <div className="ribbon-property">
-                                <input className="presBox-input" type="number" value={transitionSpeed} onKeyDown={e => e.stopPropagation()} onChange={action(e => this.updateTransitionTime(e.target.value))} /> s
+                                <input className="presBox-input" type="number" readOnly={true} value={transitionSpeed} onKeyDown={e => e.stopPropagation()} onChange={action(e => this.updateTransitionTime(e.target.value))} /> s
                             </div>
                             <div className="ribbon-propertyUpDown">
                                 <div className="ribbon-propertyUpDownItem" onClick={() => this.updateTransitionTime(String(transitionSpeed), 1000)}>
@@ -1756,6 +1757,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                             className="presBox-input"
                                             style={{ textAlign: 'center', width: '100%', height: 15, fontSize: 10 }}
                                             type="number"
+                                            readOnly={true}
                                             value={NumCast(activeItem.presStartTime).toFixed(2)}
                                             onKeyDown={e => e.stopPropagation()}
                                             onChange={action((e: React.ChangeEvent<HTMLInputElement>) => {
@@ -1782,6 +1784,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                             onKeyDown={e => e.stopPropagation()}
                                             style={{ textAlign: 'center', width: '100%', height: 15, fontSize: 10 }}
                                             type="number"
+                                            readOnly={true}
                                             value={NumCast(activeItem.presEndTime).toFixed(2)}
                                             onChange={action((e: React.ChangeEvent<HTMLInputElement>) => {
                                                 activeItem.presEndTime = Number(e.target.value);
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 4eb6aee25..2279ffe84 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -1,6 +1,6 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@material-ui/core';
-import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../../fields/Doc';
 import { Id } from '../../../../fields/FieldSymbols';
@@ -13,7 +13,7 @@ import { DocumentManager } from '../../../util/DocumentManager';
 import { DragManager } from '../../../util/DragManager';
 import { SettingsManager } from '../../../util/SettingsManager';
 import { Transform } from '../../../util/Transform';
-import { undoBatch } from '../../../util/UndoManager';
+import { undoable, undoBatch } from '../../../util/UndoManager';
 import { ViewBoxBaseComponent } from '../../DocComponent';
 import { EditableView } from '../../EditableView';
 import { Colors } from '../../global/globalEnums';
@@ -263,16 +263,15 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         }
     };
 
-    @undoBatch
-    removeItem = action((e: React.MouseEvent) => {
+    removePresentationItem = undoable((e: React.MouseEvent) => {
         e.stopPropagation();
         if (this.presBox && this.indexInPres < (this.presBoxView?.itemIndex || 0)) {
-            this.presBox.itemIndex = (this.presBoxView?.itemIndex || 0) - 1;
+            runInAction(() => (this.presBox!.itemIndex = (this.presBoxView?.itemIndex || 0) - 1));
         }
         this.props.removeDocument?.(this.rootDoc);
         this.presBoxView?.removeFromSelectedArray(this.rootDoc);
         this.removeAllRecordingInOverlay();
-    });
+    }, 'Remove doc from pres trail');
 
     // set the value/title of the individual pres element
     @undoBatch
@@ -476,7 +475,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         );
         items.push(
             <Tooltip key="trash" title={<div className="dash-tooltip">Remove from presentation</div>}>
-                <div className={'slideButton'} onClick={this.removeItem}>
+                <div className={'slideButton'} onClick={this.removePresentationItem}>
                     <FontAwesomeIcon icon={'trash'} onPointerDown={e => e.stopPropagation()} />
                 </div>
             </Tooltip>
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index d6dddf71a..5480600b0 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -395,22 +395,25 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
             ) : (
                 <>
                     <Tooltip key="trash" title={<div className="dash-tooltip">Remove Link Anchor</div>}>
-                        <button className="antimodeMenu-button" onPointerDown={this.Delete}>
+                        <button className="antimodeMenu-button" style={{ display: this.Delete === returnFalse ? 'none' : undefined }} onPointerDown={this.Delete}>
                             <FontAwesomeIcon icon="trash-alt" size="lg" />
                         </button>
                     </Tooltip>
                     <Tooltip key="Pin" title={<div className="dash-tooltip">Pin to Presentation</div>}>
-                        <button className="antimodeMenu-button" onPointerDown={this.PinToPres}>
+                        <button className="antimodeMenu-button" style={{ display: this.PinToPres === returnFalse ? 'none' : undefined }} onPointerDown={this.PinToPres}>
                             <FontAwesomeIcon icon="map-pin" size="lg" />
                         </button>
                     </Tooltip>
                     <Tooltip key="trail" title={<div className="dash-tooltip">Show Linked Trail</div>}>
-                        <button className="antimodeMenu-button" onPointerDown={this.ShowTargetTrail}>
+                        <button className="antimodeMenu-button" style={{ display: this.ShowTargetTrail === returnFalse ? 'none' : undefined }} onPointerDown={this.ShowTargetTrail}>
                             <FontAwesomeIcon icon="taxi" size="lg" />
                         </button>
                     </Tooltip>
                     <Tooltip key="toggle" title={<div className="dash-tooltip">make target visibility toggle on click</div>}>
-                        <button className="antimodeMenu-button" style={{ color: this.IsTargetToggler() ? 'black' : 'white', backgroundColor: this.IsTargetToggler() ? 'white' : 'black' }} onPointerDown={this.MakeTargetToggle}>
+                        <button
+                            className="antimodeMenu-button"
+                            style={{ display: this.IsTargetToggler === returnFalse ? 'none' : undefined, color: this.IsTargetToggler() ? 'black' : 'white', backgroundColor: this.IsTargetToggler() ? 'white' : 'black' }}
+                            onPointerDown={this.MakeTargetToggle}>
                             <FontAwesomeIcon icon="thumbtack" size="lg" />
                         </button>
                     </Tooltip>
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 1c1b41f73..3479cd20f 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -141,6 +141,8 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
                 const annos = !Field.toString(Doc.LayoutField(d) as Field).includes('CollectionView');
                 const data = d[annos ? fieldKey + '_annotations' : fieldKey];
                 data && newarray.push(...DocListCast(data));
+                const sidebar = d[fieldKey + '_sidebar'];
+                sidebar && newarray.push(...DocListCast(sidebar));
                 func(depth, d);
             });
             docs = newarray;
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 5312da009..d8447deb6 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -236,7 +236,7 @@ export class Doc extends RefField {
         if (
             doc &&
             Doc.MyFileOrphans instanceof Doc &&
-            Doc.IsDocDataProto(doc) &&
+            Doc.IsDataProto(doc) &&
             !Doc.IsSystem(doc) &&
             ![DocumentType.CONFIG, DocumentType.KVP, DocumentType.LINK, DocumentType.LINKANCHOR].includes(doc.type as any) &&
             !doc.isFolder &&
@@ -510,7 +510,7 @@ export namespace Doc {
     export function GetT<T extends Field>(doc: Doc, key: string, ctor: ToConstructor<T>, ignoreProto: boolean = false): FieldResult<T> {
         return Cast(Get(doc, key, ignoreProto), ctor) as FieldResult<T>;
     }
-    export function IsDocDataProto(doc: Doc) {
+    export function IsDataProto(doc: Doc) {
         return GetT(doc, 'isDataDoc', 'boolean', true);
     }
     export function IsBaseProto(doc: Doc) {
@@ -981,7 +981,7 @@ export namespace Doc {
     export function MakeCopy(doc: Doc, copyProto: boolean = false, copyProtoId?: string, retitle = false): Doc {
         const copy = new Doc(copyProtoId, true);
         updateCachedAcls(copy);
-        const exclude = [...Cast(doc.cloneFieldFilter, listSpec('string'), []), 'dragFactory_count', 'cloneFieldFilter'];
+        const exclude = [...StrListCast(doc.cloneFieldFilter), 'dragFactory_count', 'cloneFieldFilter'];
         Object.keys(doc).forEach(key => {
             if (exclude.includes(key)) return;
             const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
@@ -1433,7 +1433,7 @@ export namespace Doc {
     export function setDocFilter(container: Opt<Doc>, key: string, value: any, modifiers: 'remove' | 'match' | 'check' | 'x' | 'exists' | 'unset', toggle?: boolean, fieldPrefix?: string, append: boolean = true) {
         if (!container) return;
         const filterField = '_' + (fieldPrefix ? fieldPrefix + '_' : '') + 'docFilters';
-        const docFilters = Cast(container[filterField], listSpec('string'), []);
+        const docFilters = StrListCast(container[filterField]);
         runInAction(() => {
             for (let i = 0; i < docFilters.length; i++) {
                 const fields = docFilters[i].split(':'); // split key:value:modifier
@@ -1745,6 +1745,16 @@ ScriptingGlobals.add(function undo() {
     SelectionManager.DeselectAll();
     return UndoManager.Undo();
 });
+
+export function ShowUndoStack() {
+    SelectionManager.DeselectAll();
+    var buffer = '';
+    UndoManager.undoStack.forEach((batch, i) => {
+        buffer += 'Batch => ' + UndoManager.undoStackNames[i] + '\n';
+        ///batch.forEach(undo => (buffer += '   ' + undo.prop + '\n'));
+    });
+    alert(buffer);
+}
 ScriptingGlobals.add(function redo() {
     SelectionManager.DeselectAll();
     return UndoManager.Redo();
diff --git a/src/fields/util.ts b/src/fields/util.ts
index f4fd3200c..a2b445d6c 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -65,6 +65,7 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
     if (value instanceof RefField) {
         value = new ProxyField(value);
     }
+
     if (value instanceof ObjectField) {
         if (value[Parent] && value[Parent] !== receiver && !(value instanceof PrefetchProxy)) {
             throw new Error("Can't put the same object in multiple documents at the same time");
@@ -102,20 +103,24 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
             DocServer.registerDocWithCachedUpdate(receiver, prop as string, curValue);
         }
         !receiver[Initializing] &&
+            !receiver.dontUndo &&
             (!receiver[UpdatingFromServer] || receiver[ForceServerWrite]) &&
-            UndoManager.AddEvent({
-                redo: () => (receiver[prop] = value),
-                undo: () => {
-                    const wasUpdate = receiver[UpdatingFromServer];
-                    const wasForce = receiver[ForceServerWrite];
-                    receiver[ForceServerWrite] = true; // needed since writes aren't propagated to server if UpdatingFromServerIsSet
-                    receiver[UpdatingFromServer] = true; // needed if the event caused ACL's to change such that the doc is otherwise no longer editable.
-                    receiver[prop] = curValue;
-                    receiver[ForceServerWrite] = wasForce;
-                    receiver[UpdatingFromServer] = wasUpdate;
+            UndoManager.AddEvent(
+                {
+                    redo: () => (receiver[prop] = value),
+                    undo: () => {
+                        const wasUpdate = receiver[UpdatingFromServer];
+                        const wasForce = receiver[ForceServerWrite];
+                        receiver[ForceServerWrite] = true; // needed since writes aren't propagated to server if UpdatingFromServerIsSet
+                        receiver[UpdatingFromServer] = true; // needed if the event caused ACL's to change such that the doc is otherwise no longer editable.
+                        receiver[prop] = curValue;
+                        receiver[ForceServerWrite] = wasForce;
+                        receiver[UpdatingFromServer] = wasUpdate;
+                    },
+                    prop: prop?.toString(),
                 },
-                prop: prop?.toString(),
-            });
+                value
+            );
         return true;
     }
     return false;
@@ -390,6 +395,7 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any
                     diff?.op === '$addToSet'
                         ? {
                               redo: () => {
+                                  console.log('redo $add: ' + prop, diff.items); // bcz: uncomment to log undo
                                   receiver[prop].push(...diff.items.map((item: any) => item.value ?? item));
                                   lastValue = ObjectField.MakeCopy(receiver[prop]);
                               },
@@ -406,11 +412,12 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any
                                   });
                                   lastValue = ObjectField.MakeCopy(receiver[prop]);
                               }),
-                              prop: '',
+                              prop: 'add ' + diff.items.length + ' items to list',
                           }
                         : diff?.op === '$remFromSet'
                         ? {
                               redo: action(() => {
+                                  console.log('redo $rem: ' + prop, diff.items); // bcz: uncomment to log undo
                                   diff.items.forEach((item: any) => {
                                       const ind = item instanceof SchemaHeaderField ? receiver[prop].findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading) : receiver[prop].indexOf(item.value ?? item);
                                       ind !== -1 && receiver[prop].splice(ind, 1);
@@ -430,10 +437,11 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any
                                   });
                                   lastValue = ObjectField.MakeCopy(receiver[prop]);
                               },
-                              prop: '',
+                              prop: 'remove ' + diff.items.length + ' items from list',
                           }
                         : {
                               redo: () => {
+                                  console.log('redo list: ' + prop, receiver[prop]); // bcz: uncomment to log undo
                                   receiver[prop] = ObjectField.MakeCopy(newValue as List<any>);
                                   lastValue = ObjectField.MakeCopy(receiver[prop]);
                               },
@@ -442,7 +450,7 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any
                                   receiver[prop] = ObjectField.MakeCopy(prevValue as List<any>);
                                   lastValue = ObjectField.MakeCopy(receiver[prop]);
                               },
-                              prop: '',
+                              prop: 'assign list',
                           }
                 );
         }
-- 
cgit v1.2.3-70-g09d2


From fb41a1fd07faad49c88b2a8dc48b730a1609b00d Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 17 May 2023 09:43:38 -0400
Subject: fixed dragging marquee on images which blew up layout_scrollTop

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

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 95046661e..9bf071cc0 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1131,7 +1131,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 const relTop = NumCast(this.rootDoc.layout_scrollTop) / maxScrollTop;
                 this.rootDoc.layout_scrollTop = undefined;
                 newPanY = minPanY + relTop * (maxPanY - minPanY);
-            } else if (fitYscroll && this.rootDoc.layout_scrollTop === undefined && NumCast(this.rootDoc._freeform_scale, minScale) === minScale) {
+            } else if ( fitYscroll >2 && this.rootDoc.layout_scrollTop === undefined && NumCast(this.rootDoc._freeform_scale, minScale) === minScale) {
                 const maxPanY = minPanY + fitYscroll;
                 const relTop = (panY - minPanY) / (maxPanY - minPanY);
                 setTimeout(() => (this.rootDoc.layout_scrollTop = relTop * maxScrollTop), 10);
-- 
cgit v1.2.3-70-g09d2


From da5184d2113935df25633a6015e177a51e88df2f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 17 May 2023 16:18:20 -0400
Subject: fixed scrolling/panning on fitWidth images.  fixed drag/zoom
 conflicts for overlayDocs that are pannable.

---
 src/client/documents/Documents.ts                  |  6 +-
 src/client/util/DocumentManager.ts                 |  2 +-
 src/client/views/DocumentDecorations.tsx           |  6 +-
 src/client/views/MarqueeAnnotator.tsx              |  6 +-
 src/client/views/OverlayView.tsx                   |  5 +-
 src/client/views/PropertiesView.tsx                |  8 +--
 .../views/collections/CollectionDockingView.tsx    |  2 +-
 src/client/views/collections/CollectionMenu.tsx    |  1 -
 src/client/views/collections/TabDocView.tsx        |  5 +-
 src/client/views/collections/TreeView.tsx          |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 71 +++++++++++-----------
 .../collectionSchema/CollectionSchemaView.tsx      | 21 ++++---
 src/client/views/nodes/AudioBox.tsx                |  4 +-
 src/client/views/nodes/DocumentView.tsx            |  2 +-
 src/client/views/nodes/KeyValuePair.scss           |  4 --
 src/client/views/nodes/KeyValuePair.tsx            |  2 +-
 src/client/views/nodes/VideoBox.tsx                | 12 ++--
 src/client/views/nodes/WebBox.tsx                  |  1 +
 .../views/nodes/formattedText/FormattedTextBox.tsx | 29 ++++-----
 src/client/views/nodes/trails/PresBox.scss         |  1 +
 src/client/views/nodes/trails/PresBox.tsx          | 24 ++++----
 src/client/views/nodes/trails/PresElementBox.tsx   | 18 +++---
 src/fields/Doc.ts                                  |  9 +++
 23 files changed, 124 insertions(+), 117 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 5aa69e499..35747f746 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -450,7 +450,7 @@ export namespace Docs {
                 DocumentType.IMG,
                 {
                     layout: { view: ImageBox, dataField: defaultDataKey },
-                    options: {},
+                    options: {freeform: '',},
                 },
             ],
             [
@@ -464,7 +464,7 @@ export namespace Docs {
                 DocumentType.COL,
                 {
                     layout: { view: CollectionView, dataField: defaultDataKey },
-                    options: { _layout_fitWidth: true, _freeform_panX: 0, _freeform_panY: 0, _freeform_scale: 1 },
+                    options: { _layout_fitWidth: true, freeform: '', _freeform_panX: 0, _freeform_panY: 0, _freeform_scale: 1 },
                 },
             ],
             [
@@ -1035,7 +1035,7 @@ export namespace Docs {
         // }
 
         export function FreeformDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
-            const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _xPadding: 20, _yPadding: 20, freeform: '', ...options, _type_collection: CollectionViewType.Freeform }, id);
+            const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _xPadding: 20, _yPadding: 20,  ...options, _type_collection: CollectionViewType.Freeform }, id);
             documents.forEach(d => (d.embedContainer = inst));
             return inst;
         }
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index ea5770b6a..642ea26da 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -318,7 +318,7 @@ export function DocFocusOrOpen(doc: Doc, options: DocFocusOptions = { willZoomCe
         if (dv && (!containingDoc || dv.props.docViewPath().lastElement()?.Document === containingDoc)) {
             DocumentManager.Instance.showDocumentView(dv, options).then(() => dv && Doc.linkFollowHighlight(dv.rootDoc));
         } else {
-            const container = DocCast(containingDoc ?? doc.embedContainer);
+            const container = DocCast(containingDoc ?? doc.embedContainer ?? doc);
             const showDoc = !Doc.IsSystem(container) ? container : doc;
             options.toggleTarget = undefined;
             DocumentManager.Instance.showDocument(showDoc, options, () => DocumentManager.Instance.showDocument(doc, { ...options, openLocation: undefined })).then(() => {
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 8077b9af1..beddc0e8a 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -10,7 +10,7 @@ import { AclAdmin, AclEdit, DataSym, Doc, DocListCast, Field, HeightSym, WidthSy
 import { InkField } from '../../fields/InkField';
 import { RichTextField } from '../../fields/RichTextField';
 import { ScriptField } from '../../fields/ScriptField';
-import { Cast, NumCast, StrCast } from '../../fields/Types';
+import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
 import { GetEffectiveAcl } from '../../fields/util';
 import { aggregateBounds, emptyFunction, numberValue, returnFalse, setupMoveUpEvents, Utils } from '../../Utils';
 import { Docs } from '../documents/Documents';
@@ -189,7 +189,9 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
     @action
     onBackgroundMove = (dragTitle: boolean, e: PointerEvent): boolean => {
         const dragDocView = SelectionManager.Views()[0];
-        if (DocListCast(Doc.MyOverlayDocs.data).includes(dragDocView.rootDoc)) return false;
+        const containers = new Set<Doc|undefined>();
+        SelectionManager.Views().forEach(v => containers.add(DocCast(v.rootDoc.embedContainer)));
+        if (containers.size > 1) return false;
         const { left, top } = dragDocView.getBounds() || { left: 0, top: 0 };
         const dragData = new DragManager.DocumentDragData(
             SelectionManager.Views().map(dv => dv.props.Document),
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 1d6c94c13..c9555ab91 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -153,8 +153,8 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
                 annotationOn: this.props.rootDoc,
                 title: 'Annotation on ' + this.props.rootDoc.title,
             });
-            marqueeAnno.x = NumCast(this.props.docView.props.Document.freeform_panXMin) + (parseInt(anno.style.left || '0') - containerOffset[0]) / scale / NumCast(this.props.docView.props.Document._freeform_scale, 1);
-            marqueeAnno.y = NumCast(this.props.docView.props.Document.freeform_panYMin) + (parseInt(anno.style.top || '0') - containerOffset[1]) / scale / NumCast(this.props.docView.props.Document._freeform_scale, 1) + NumCast(this.props.scrollTop);
+            marqueeAnno.x = NumCast(this.props.docView.props.Document.freeform_panX_min) + (parseInt(anno.style.left || '0') - containerOffset[0]) / scale / NumCast(this.props.docView.props.Document._freeform_scale, 1);
+            marqueeAnno.y = NumCast(this.props.docView.props.Document.freeform_panY_min) + (parseInt(anno.style.top || '0') - containerOffset[1]) / scale / NumCast(this.props.docView.props.Document._freeform_scale, 1) + NumCast(this.props.scrollTop);
             marqueeAnno._height = parseInt(anno.style.height || '0') / scale / NumCast(this.props.docView.props.Document._freeform_scale, 1);
             marqueeAnno._width = parseInt(anno.style.width || '0') / scale / NumCast(this.props.docView.props.Document._freeform_scale, 1);
             anno.remove();
@@ -243,7 +243,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
         this._top = Math.min(this._startY, this._startY + this._height);
         this._width = Math.abs(this._width);
         this._height = Math.abs(this._height);
-        e.stopPropagation();
+        //e.stopPropagation(); // overlay documents are all 'active', yet they can be dragged. if we stop propagation, then they can be marqueed but not dragged.  if we don't stop, then they will be marqueed and dragged, but the marquee will be zero width since the doc will move along with the cursor.
     };
 
     onSelectEnd = (e: PointerEvent) => {
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 9baedae6d..00f9aa879 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -159,7 +159,7 @@ export class OverlayView extends React.Component {
     }
 
     removeOverlayDoc = (doc: Doc | Doc[]) => {
-        (doc instanceof Doc ? [doc] : doc).forEach(doc => Doc.RemoveDocFromList(Doc.MyOverlayDocs, undefined, doc));
+        (doc instanceof Doc ? [doc] : doc).forEach(Doc.RemFromMyOverlay);
         return true;
     };
 
@@ -177,6 +177,7 @@ export class OverlayView extends React.Component {
                     offsety = 0;
                 const dref = React.createRef<HTMLDivElement>();
                 const onPointerMove = action((e: PointerEvent, down: number[]) => {
+                 if (e.cancelBubble) return false;  // if the overlay doc processed the move event (e.g., to pan its contents), then the event should be marked as canceled since propagation can't be stopped
                     if (e.buttons === 1) {
                         d.overlayX = e.clientX + offsetx;
                         d.overlayY = e.clientY + offsety;
@@ -196,7 +197,7 @@ export class OverlayView extends React.Component {
                 });
 
                 const onPointerDown = (e: React.PointerEvent) => {
-                    setupMoveUpEvents(this, e, onPointerMove, emptyFunction, emptyFunction);
+                    setupMoveUpEvents(this, e, onPointerMove, emptyFunction, emptyFunction, false);
                     offsetx = NumCast(d.overlayX) - e.clientX;
                     offsety = NumCast(d.overlayY) - e.clientY;
                 };
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 75514a090..0c89f1a27 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -1623,8 +1623,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                     </div>
                 );
             }
-            if (this.isPres) {
-                const selectedItem: boolean = PresBox.Instance?.selectedArray.size > 0;
+            if (this.isPres && PresBox.Instance) {
+                const selectedItem: boolean = PresBox.Instance.selectedArray.size > 0;
                 const type = [DocumentType.AUDIO, DocumentType.VID].includes(DocCast(PresBox.Instance.activeItem?.annotationOn)?.type as any as DocumentType)
                     ? (DocCast(PresBox.Instance.activeItem?.annotationOn)?.type as any as DocumentType)
                     : PresBox.targetRenderedDoc(PresBox.Instance.activeItem)?.type;
@@ -1636,8 +1636,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                         <div className="propertiesView-name" style={{ borderBottom: 0 }}>
                             {this.editableTitle}
                             <div className="propertiesView-presSelected">
-                                <div className="propertiesView-selectedCount">{PresBox.Instance?.selectedArray.size} selected</div>
-                                <div className="propertiesView-selectedList">{PresBox.Instance?.listOfSelected}</div>
+                                <div className="propertiesView-selectedCount">{PresBox.Instance.selectedArray.size} selected</div>
+                                <div className="propertiesView-selectedList">{PresBox.Instance.listOfSelected}</div>
                             </div>
                         </div>
                         {!selectedItem ? null : (
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 76dadc76d..e9cc2c894 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -593,7 +593,7 @@ ScriptingGlobals.add(
                     case '<ScriptingRepl />': return OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: 'Scripting REPL' });
                     case "<UndoStack>": return OverlayView.Instance.addWindow(<UndoStack />, { x: 300, y: 100, width: 200, height: 200, title: 'Scripting REPL' });
                 }
-                Doc.AddDocToList(Doc.MyOverlayDocs, undefined, doc);
+                Doc.AddToMyOverlay(doc);
         }
     },
     'opens up document in location specified',
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 570d9c8fb..d2338d742 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -549,7 +549,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
     @action
     startRecording = () => {
         const doc = Docs.Create.ScreenshotDocument({ title: 'screen recording', _layout_fitWidth: true, _width: 400, _height: 200, mediaState: 'pendingRecording' });
-        //Doc.AddDocToList(Doc.MyOverlayDocs, undefined, doc);
         CollectionDockingView.AddSplit(doc, OpenWhereMod.right);
     };
 
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 33fa434e1..b20d05433 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -292,13 +292,12 @@ export class TabDocView extends React.Component<TabDocViewProps> {
             PresBox.Instance?.clearSelectedArray();
             pinDoc && PresBox.Instance?.addToSelectedArray(pinDoc); //Update selected array
         });
-        if (
+        if ( // open the presentation trail if it's not already opened
             !Array.from(CollectionDockingView.Instance?.tabMap ?? [])
                 .map(d => d.DashDoc)
                 .includes(curPres)
         ) {
-            const docs = Cast(Doc.MyOverlayDocs.data, listSpec(Doc), []);
-            if (docs.includes(curPres)) docs.splice(docs.indexOf(curPres), 1);
+            if (Doc.IsInMyOverlay(curPres)) Doc.RemFromMyOverlay(curPres);
             CollectionDockingView.AddSplit(curPres, OpenWhereMod.right);
             setTimeout(() => DocumentManager.Instance.showDocument(docList.lastElement(), { willPan: true }), 100); // keeps the pinned doc in view since the sidebar shifts things
         }
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index f56eaee07..abbb0df4c 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -761,7 +761,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         const makeFolder = { script: ScriptField.MakeFunction(`scriptContext.makeFolder()`, { scriptContext: 'any' })!, icon: 'folder-plus', label: 'New Folder' };
         const deleteItem = { script: ScriptField.MakeFunction(`scriptContext.deleteItem()`, { scriptContext: 'any' })!, icon: 'folder-plus', label: 'Delete' };
         const folderOp = this.childDocs?.length ? [makeFolder] : [];
-        const openEmbedding = { script: ScriptField.MakeFunction(`openDoc(getEmbedding(self), ${OpenWhere.addRight})`)!, icon: 'copy', label: 'Open Embedding' };
+        const openEmbedding = { script: ScriptField.MakeFunction(`openDoc(getEmbedding(self), ${OpenWhere.addRight})`)!, icon: 'copy', label: 'Open New Embedding' };
         const focusDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, icon: 'eye', label: 'Focus or Open' };
         return [
             ...(this.props.contextMenuItems ?? []).filter(mi => (!mi.filter ? true : mi.filter.script.run({ doc: this.doc })?.result)),
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 9bf071cc0..3b5dd45d9 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -635,7 +635,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     case InkTool.None:
                         if (!(this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine))) {
                             this._hitCluster = this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY));
-                            setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, this._hitCluster !== -1 ? true : false);
+                            setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, this._hitCluster !== -1 ? true : false, false);
                         }
                         break;
                 }
@@ -868,9 +868,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             Doc.ActiveTool = InkTool.None;
         } else {
             if (this.tryDragCluster(e, this._hitCluster)) {
+                e.stopPropagation(); // we're moving a cluster, so stop propagation and return true to end panning and let the document drag take over
                 return true;
             }
-            this.pan(e);
+            // pan the view if this is a regular collection, or it's an overlay and the overlay is zoomed (otherwise, there's nothing to pan)
+            if (!this.props.isAnnotationOverlay || (1 - NumCast(this.rootDoc._freeform_scale_min, 1)/ this.getLocalTransform().inverse().Scale)) {
+                this.pan(e);                
+                e.stopPropagation();  // if we are actually panning, stop propagation -- this will preven things like the overlayView from dragging the document while we're panning
+            }
         }
         return false;
     };
@@ -1023,12 +1028,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         if (deltaScale * invTransform.Scale > 20) {
             deltaScale = 20 / invTransform.Scale;
         }
-        if (deltaScale < 1 && invTransform.Scale <= NumCast(this.rootDoc._freeform_scaleMin, 1) && this.isAnnotationOverlay) {
+        if (deltaScale < 1 && invTransform.Scale <= NumCast(this.rootDoc._freeform_scale_min, 1) && this.isAnnotationOverlay) {
             this.setPan(0, 0);
             return;
         }
-        if (deltaScale * invTransform.Scale < NumCast(this.rootDoc._freeform_scaleMin, 1) && this.isAnnotationOverlay) {
-            deltaScale = NumCast(this.rootDoc._freeform_scaleMin, 1) / invTransform.Scale;
+        if (deltaScale * invTransform.Scale < NumCast(this.rootDoc._freeform_scale_min, 1) && this.isAnnotationOverlay) {
+            deltaScale = NumCast(this.rootDoc._freeform_scale_min, 1) / invTransform.Scale;
         }
 
         const localTransform = invTransform.scaleAbout(deltaScale, x, y);
@@ -1040,36 +1045,25 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     };
 
     @action
-    onPointerWheel = (e: React.WheelEvent): void => {
+    onPointerWheel = (e:React.WheelEvent): void => {
         if (this.Document._isGroup || !this.isContentActive()) return; // group style collections neither pan nor zoom
         PresBox.Instance?.pauseAutoPres();
-        if (this.layoutDoc._Transform || DocListCast(Doc.MyOverlayDocs?.data).includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return;
+        if (this.layoutDoc._Transform || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return;
         e.stopPropagation();
+        const docHeight = NumCast(this.rootDoc[Doc.LayoutFieldKey(this.rootDoc)+"_nativeHeight"], this.nativeHeight);
+        const scrollable = NumCast(this.layoutDoc[this.scaleFieldKey],1) ===1 && docHeight > this.props.PanelHeight() / this.nativeDimScaling ;
         switch (!e.ctrlKey ? Doc.UserDoc().freeformScrollMode : freeformScrollMode.Pan) {
             case freeformScrollMode.Pan:
                 // if ctrl is selected then zoom
-                if (e.ctrlKey) {
-                    if (this.props.isContentActive(true)) {
-                        if (this.props.isAnnotationOverlayScrollable) {
-                            // bcz: zooming on a webbox doesn't get the correct coordinates here for unknown reasons.
-                            // so better to do nothing than having things jump around.
-                        } else this.zoom(e.screenX, e.screenY, e.deltaY);
-                    }
-                } // otherwise pan
-                else if (this.props.isContentActive(true)) {
-                    const dx = -e.deltaX;
-                    const dy = -e.deltaY;
-                    if (e.shiftKey) {
-                        !this.props.isAnnotationOverlayScrollable && this.scrollPan({ deltaX: dy, deltaY: 0 });
-                    } else {
-                        !this.props.isAnnotationOverlayScrollable && this.scrollPan({ deltaX: dx, deltaY: dy });
-                    }
+                if (!e.ctrlKey && this.props.isContentActive(true)) {
+                    this.scrollPan({ deltaX: -e.deltaX, deltaY: e.shiftKey ? 0 :-Math.max(-1, Math.min(1,e.deltaY)) });
+                    break;
                 }
-                break;
             default:
             case freeformScrollMode.Zoom:
-                if (this.props.isContentActive(true)) {
-                    !this.props.isAnnotationOverlayScrollable && this.zoom(e.clientX, e.clientY, e.deltaY); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
+                if ((e.ctrlKey || !scrollable) && this.props.isContentActive(true)) {
+                    this.zoom(e.clientX, e.clientY,Math.max(-1, Math.min(1,e.deltaY))); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
+                    e.preventDefault();
                 }
                 break;
         }
@@ -1112,19 +1106,20 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 else if (ranges.yrange.max <= panY - panelHgtMin / 2) panY = ranges.yrange.min - (this.props.originTopLeft ? panelHgtMax / 2 : panelHgtMin / 2);
             }
         }
-        if (!this.layoutDoc._lockedTransform || LightboxView.LightboxDoc || DocListCast(Doc.MyOverlayDocs?.data).includes(this.Document)) {
+        if (!this.layoutDoc._lockedTransform || LightboxView.LightboxDoc) {
             this.setPanZoomTransition(panTime);
-            const scale = this.getLocalTransform().inverse().Scale;
-            const minScale = NumCast(this.rootDoc._freeform_scaleMin, 1);
-            const minPanX = NumCast(this.rootDoc._freeform_panXMin, 0);
-            const minPanY = NumCast(this.rootDoc._freeform_panYMin, 0);
-            const newPanX = Math.min(minPanX + (1 - minScale / scale) * NumCast(this.rootDoc._freeform_panXMax, this.nativeWidth), Math.max(minPanX, panX));
+            const minScale = NumCast(this.rootDoc._freeform_scale_min, 1);
+            const scale = (1 - minScale/ this.getLocalTransform().inverse().Scale);
+            const minPanX = NumCast(this.rootDoc._freeform_panX_min, 0);
+            const minPanY = NumCast(this.rootDoc._freeform_panY_min, 0);
+            const maxPanX = NumCast(this.rootDoc._freeform_panX_max, this.nativeWidth);
+            const newPanX = Math.min(minPanX + scale *maxPanX, Math.max(minPanX, panX));
             const fitYscroll = (((this.nativeHeight / this.nativeWidth) * this.props.PanelWidth() - this.props.PanelHeight()) * this.props.ScreenToLocalTransform().Scale) / minScale;
             const nativeHeight = (this.props.PanelHeight() / this.props.PanelWidth() / (this.nativeHeight / this.nativeWidth)) * this.nativeHeight;
             const maxScrollTop = this.nativeHeight / this.props.ScreenToLocalTransform().Scale - this.props.PanelHeight();
             const maxPanY =
                 minPanY + // minPanY + scrolling introduced by view scaling + scrolling introduced by layout_fitWidth
-                (1 - minScale / scale) * NumCast(this.rootDoc._panYMax, nativeHeight) +
+                scale* NumCast(this.rootDoc._panY_max, nativeHeight) +
                 (!this.props.getScrollHeight?.() ? fitYscroll : 0); // when not zoomed, scrolling is handled via a scrollbar, not panning
             let newPanY = Math.max(minPanY, Math.min(maxPanY, panY));
             if (false && NumCast(this.rootDoc.layout_scrollTop) && NumCast(this.rootDoc._freeform_scale, minScale) !== minScale) {
@@ -1971,6 +1966,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     lightboxPanelWidth = () => Math.max(0, this.props.PanelWidth() - 30);
     lightboxPanelHeight = () => Math.max(0, this.props.PanelHeight() - 30);
     lightboxScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-15, -15);
+    onPassiveWheel = (e:WheelEvent) => {
+        const docHeight = NumCast(this.rootDoc[Doc.LayoutFieldKey(this.rootDoc)+"_nativeHeight"], this.nativeHeight);
+        const scrollable = NumCast(this.layoutDoc[this.scaleFieldKey],1) ===1 && docHeight > this.props.PanelHeight() / this.nativeDimScaling ;
+        this.props.isSelected() && !scrollable && e.preventDefault()
+    }
+    _oldWheel:any;
     render() {
         TraceMobx();
         return (
@@ -1978,8 +1979,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 className="collectionfreeformview-container"
                 ref={r => {
                     this.createDashEventsTarget(r);
+                    this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
+                    this._oldWheel= r;
                     // prevent wheel events from passivly propagating up through containers
-                    !this.props.isAnnotationOverlay && r?.addEventListener('wheel', (e: WheelEvent) => this.props.isSelected() && e.preventDefault(), { passive: false });
+                     r?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
                 }}
                 onWheel={this.onPointerWheel}
                 onClick={this.onClick}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 2a68f9b58..1cc48bebe 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -686,6 +686,12 @@ export class CollectionSchemaView extends CollectionSubView() {
         );
     }
 
+    onPassiveWheel = (e:WheelEvent) => { 
+        // 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._oldWheel.scrollTop && e.deltaY <= 0) e.preventDefault();
+        e.stopPropagation();
+    }
+    _oldWheel:any;
     @computed get keysDropdown() {
         return (
             <div className="schema-key-search">
@@ -699,16 +705,11 @@ export class CollectionSchemaView extends CollectionSubView() {
                 </div>
                 <div
                     className="schema-key-list"
-                    ref={r =>
-                        r?.addEventListener(
-                            'wheel', // 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)
-                            (e: WheelEvent) => {
-                                if (!r.scrollTop && e.deltaY <= 0) e.preventDefault();
-                                e.stopPropagation();
-                            },
-                            { passive: false }
-                        )
-                    }>
+                    ref={r => {
+                        this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
+                        this._oldWheel= r;
+                        r?.addEventListener('wheel', this.onPassiveWheel, { passive: false } );
+                    }}>
                     {this._menuKeys.map(key => (
                         <div
                             className="schema-search-result"
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 410e0bbdc..0cb849923 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -362,10 +362,10 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                 Doc.GetProto(newDoc).recordingSource = this.dataDoc;
                 Doc.GetProto(newDoc).recordingStart = ComputedField.MakeFunction(`self.recordingSource["${this.fieldKey}-recordingStart"]`);
                 Doc.GetProto(newDoc).mediaState = ComputedField.MakeFunction('self.recordingSource.mediaState');
-                if (DocListCast(Doc.MyOverlayDocs?.data).includes(this.rootDoc)) {
+                if (Doc.IsInMyOverlay(this.rootDoc)) {
                     newDoc.overlayX = this.rootDoc.x;
                     newDoc.overlayY = NumCast(this.rootDoc.y) + NumCast(this.rootDoc._height);
-                    Doc.AddDocToList(Doc.MyOverlayDocs, undefined, newDoc);
+                    Doc.AddToMyOverlay(newDoc);
                 } else {
                     this.props.addDocument?.(newDoc);
                 }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 10ef1e6af..84f82a4b9 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -527,7 +527,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 !this.Document.ignoreClick &&
                 e.button === 0 &&
                 this.pointerEvents !== 'none' &&
-                !DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)
+                !Doc.IsInMyOverlay(this.layoutDoc)
             ) {
                 e.stopPropagation();
                 // don't preventDefault anymore.  Goldenlayout, PDF text selection and RTF text selection all need it to go though
diff --git a/src/client/views/nodes/KeyValuePair.scss b/src/client/views/nodes/KeyValuePair.scss
index 2b4867321..57d36932e 100644
--- a/src/client/views/nodes/KeyValuePair.scss
+++ b/src/client/views/nodes/KeyValuePair.scss
@@ -19,7 +19,6 @@
             position: relative;
             margin: 0;
         }
-        .keyValuePair-keyFieldMod,
         .keyValuePair-keyField {
             width: 100%;
             margin-left: 20px;
@@ -28,9 +27,6 @@
             overflow: auto;
             display: inline;
         }
-        .keyValuePair-keyFieldMod {
-            margin-left: 35px;
-        }
     }
 }
 .keyValuePair-td-value {
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 509bb667e..91df928c4 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -109,7 +109,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
                             X
                         </button>
                         <input className="keyValuePair-td-key-check" type="checkbox" style={hover} onChange={this.handleCheck} ref={this.checkbox} />
-                        <div className={`keyValuePair-keyField${props.fieldKey.includes('_') ? 'Mod' : ''}`} style={{ color: keyStyle }}>
+                        <div className='keyValuePair-keyField' style={{ marginLeft: 35 * (props.fieldKey.match(/_/g)?.length ||0), color: keyStyle }}>
                             {'('.repeat(parenCount)}
                             {props.fieldKey}
                             {')'.repeat(parenCount)}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 3cfc3d696..9cf929679 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -870,7 +870,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                 }),
                 returnFalse,
                 () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations),
-                false
+                false, false
             );
         }
     };
@@ -1026,13 +1026,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         croppingProto.videoCrop = true;
         croppingProto.layout_currentTimecode = this.layoutDoc._layout_currentTimecode;
         croppingProto.freeform_scale = viewScale;
-        croppingProto.freeform_scaleMin = viewScale;
+        croppingProto.freeform_scale_min = viewScale;
         croppingProto.freeform_ = anchx / viewScale;
         croppingProto.freeform_panY = anchy / viewScale;
-        croppingProto.freeform_panXMin = anchx / viewScale;
-        croppingProto.freeform_panXMax = anchw / viewScale;
-        croppingProto.freeform_panYMin = anchy / viewScale;
-        croppingProto.freeform_panYMax = anchh / 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;
         if (addCrop) {
             DocUtils.MakeLink(region, cropping, { link_relationship: 'cropped image' });
         }
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 437079ca4..48808dbad 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -745,6 +745,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     ref={action((r: HTMLIFrameElement | null) => (this._iframe = r))}
                     src={url}
                     onLoad={this.iframeLoaded}
+                    scrolling="no" // ugh.. on windows, I get an inner scroll bar for the iframe's body even though the scrollHeight should be set to the full height of the document.  
                     // the 'allow-top-navigation' and 'allow-top-navigation-by-user-activation' attributes are left out to prevent iframes from redirecting the top-level Dash page
                     // sandbox={"allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin allow-scripts"} />;
                     sandbox={`${this.layoutDoc.allowScripts ? 'allow-scripts' : ''} allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin`}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index e85835002..fb0c0d2ab 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -917,10 +917,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     data_nativeWidth: result.nativeWidth,
                     data_nativeHeight: result.nativeHeight,
                 });
-                if (DocListCast(Doc.MyOverlayDocs?.data).includes(this.rootDoc)) {
+                if (Doc.IsInMyOverlay(this.rootDoc)) {
                     newDoc.overlayX = this.rootDoc.x;
                     newDoc.overlayY = NumCast(this.rootDoc.y) + NumCast(this.rootDoc._height);
-                    Doc.AddDocToList(Doc.MyOverlayDocs, undefined, newDoc);
+                    Doc.AddToMyOverlay(newDoc);
                 } else {
                     this.props.addDocument?.(newDoc);
                 }
@@ -2000,6 +2000,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         return this.props.fieldKey + (usePath && (!usePath.includes(':hover') || this._isHovering) ? `_${usePath.replace(':hover', '')}` : '');
     }
     @observable _isHovering = false;
+    onPassiveWheel = (e:WheelEvent) => { 
+        // 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() && !this.props.allowScroll) {
+            if (!NumCast(this.layoutDoc._layout_scrollTop) && e.deltaY <= 0) e.preventDefault();
+            e.stopPropagation();
+        }
+    }
+    _oldWheel:any;
     render() {
         TraceMobx();
         const active = this.props.isContentActive() || this.props.isSelected();
@@ -2019,18 +2027,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 className="formattedTextBox"
                 onPointerEnter={action(() => (this._isHovering = true))}
                 onPointerLeave={action(() => (this._isHovering = false))}
-                ref={r =>
-                    r?.addEventListener(
-                        'wheel', // 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)
-                        (e: WheelEvent) => {
-                            if (this.props.isContentActive() && !this.props.allowScroll) {
-                                if (!NumCast(this.layoutDoc._layout_scrollTop) && e.deltaY <= 0) e.preventDefault();
-                                e.stopPropagation();
-                            }
-                        },
-                        { passive: false }
-                    )
-                }
+                ref={r => {
+                    this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
+                    this._oldWheel= r;
+                    r?.addEventListener('wheel', this.onPassiveWheel,  { passive: false } );
+                }}
                 style={{
                     ...(this.props.dontScale
                         ? {}
diff --git a/src/client/views/nodes/trails/PresBox.scss b/src/client/views/nodes/trails/PresBox.scss
index eb91c82f3..bf56b4d9e 100644
--- a/src/client/views/nodes/trails/PresBox.scss
+++ b/src/client/views/nodes/trails/PresBox.scss
@@ -951,6 +951,7 @@
         margin-right: unset;
         height: 100%;
         position: relative;
+        user-select: none;
     }
 
     select {
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 14d463a6b..913018b69 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -196,7 +196,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         this.turnOffEdit(true);
         this._disposers.selection = reaction(
             () => SelectionManager.Views(),
-            views => views.some(view => view.props.Document === this.rootDoc) && this.updateCurrentPresentation()
+            views => (!PresBox.Instance || views.some(view => view.props.Document === this.rootDoc)) && this.updateCurrentPresentation(),
+            {fireImmediately:true}
         );
         this._disposers.editing = reaction(
             () => this.layoutDoc.presStatus === PresStatus.Edit,
@@ -832,7 +833,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     }
                 });
             LightboxView.SetLightboxDoc(undefined);
-            Doc.RemoveDocFromList(Doc.MyOverlayDocs, undefined, this.rootDoc);
+            Doc.RemFromMyOverlay(this.rootDoc);
             return PresStatus.Edit;
         };
     };
@@ -928,8 +929,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return PresBox.OpenPresMinimized(this.rootDoc, [pt[0] + (this.props.PanelWidth() - 250), pt[1] + 10]);
     };
     exitMinimize = () => {
-        if (DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)) {
-            Doc.RemoveDocFromList(Doc.MyOverlayDocs, undefined, this.rootDoc);
+        if (Doc.IsInMyOverlay(this.layoutDoc)) {
+            Doc.RemFromMyOverlay(this.rootDoc);
             CollectionDockingView.AddSplit(this.rootDoc, OpenWhereMod.right);
         }
         return PresStatus.Edit;
@@ -941,7 +942,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         doc.overlayY = pt[1];
         doc._height = 30;
         doc._width = PresBox.minimizedWidth;
-        Doc.AddDocToList(Doc.MyOverlayDocs, undefined, doc);
+        Doc.AddToMyOverlay(doc);
         PresBox.Instance?.initializePresState(PresBox.Instance.itemIndex);
         return (doc.presStatus = PresStatus.Manual);
     }
@@ -1158,7 +1159,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 }
                 break;
             case 'Escape':
-                if (DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)) {
+                if (Doc.IsInMyOverlay(this.layoutDoc)) {
                     this.exitClicked();
                 } else if (this.layoutDoc.presStatus === PresStatus.Edit) {
                     this.clearSelectedArray();
@@ -2130,10 +2131,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const propTitle = SettingsManager.propertiesWidth > 0 ? 'Close Presentation Panel' : 'Open Presentation Panel';
         const mode = StrCast(this.rootDoc._type_collection) as CollectionViewType;
         const isMini: boolean = this.toolbarWidth <= 100;
-        const inOverlay = DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc);
         const activeColor = Colors.LIGHT_BLUE;
         const inactiveColor = Colors.WHITE;
-        return mode === CollectionViewType.Carousel3D || inOverlay ? null : (
+        return mode === CollectionViewType.Carousel3D || Doc.IsInMyOverlay(this.rootDoc) ? null : (
             <div id="toolbarContainer" className={'presBox-toolbar'}>
                 {/* <Tooltip title={<><div className="dash-tooltip">{"Add new slide"}</div></>}><div className={`toolbar-button ${this.newDocumentTools ? "active" : ""}`} onClick={action(() => this.newDocumentTools = !this.newDocumentTools)}>
                     <FontAwesomeIcon icon={"plus"} />
@@ -2174,9 +2174,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @computed get topPanel() {
         const mode = StrCast(this.rootDoc._type_collection) as CollectionViewType;
         const isMini: boolean = this.toolbarWidth <= 100;
-        const inOverlay = DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc);
         return (
-            <div className={`presBox-buttons${inOverlay ? ' inOverlay' : ''}`} style={{ background: Doc.ActivePresentation === this.rootDoc ? Colors.LIGHT_BLUE : undefined, display: !this.rootDoc._chromeHidden ? 'none' : undefined }}>
+            <div className={`presBox-buttons${Doc.IsInMyOverlay(this.rootDoc) ? ' inOverlay' : ''}`} style={{ background: Doc.ActivePresentation === this.rootDoc ? Colors.LIGHT_BLUE : undefined, display: !this.rootDoc._chromeHidden ? 'none' : undefined }}>
                 {isMini ? null : (
                     <select className="presBox-viewPicker" style={{ display: this.layoutDoc.presStatus === 'edit' ? 'block' : 'none' }} onPointerDown={e => e.stopPropagation()} onChange={this.viewChanged} value={mode}>
                         <option onPointerDown={StopEvent} value={CollectionViewType.Stacking}>
@@ -2226,7 +2225,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @computed get playButtons() {
         const presEnd = !this.layoutDoc.presLoop && this.itemIndex === this.childDocs.length - 1 && (this.activeItem.presIndexed === undefined || NumCast(this.activeItem.presIndexed) === (this.progressivizedItems(this.activeItem)?.length ?? 0));
         const presStart: boolean = !this.layoutDoc.presLoop && this.itemIndex === 0;
-        const inOverlay = DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc);
+        const inOverlay = Doc.IsInMyOverlay(this.rootDoc);
         // Case 1: There are still other frames and should go through all frames before going to next slide
         return (
             <div className="presPanelOverlay" style={{ display: this.layoutDoc.presStatus !== 'edit' ? 'inline-flex' : 'none' }}>
@@ -2400,7 +2399,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const mode = StrCast(this.rootDoc._type_collection) as CollectionViewType;
         const presEnd = !this.layoutDoc.presLoop && this.itemIndex === this.childDocs.length - 1 && (this.activeItem.presIndexed === undefined || NumCast(this.activeItem.presIndexed) === (this.progressivizedItems(this.activeItem)?.length ?? 0));
         const presStart = !this.layoutDoc.presLoop && this.itemIndex === 0;
-        const inOverlay = DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc);
         return this.props.addDocTab === returnFalse ? ( // bcz: hack!! - addDocTab === returnFalse only when this is being rendered by the OverlayView which means the doc is a mini player
             <div className="miniPres" onClick={e => e.stopPropagation()} onPointerEnter={action(e => (this._forceKeyEvents = true))}>
                 <div
@@ -2443,7 +2441,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 </div>
             </div>
         ) : (
-            <div className="presBox-cont" style={{ minWidth: inOverlay ? PresBox.minimizedWidth : undefined }}>
+            <div className="presBox-cont" style={{ minWidth: Doc.IsInMyOverlay(this.rootDoc) ? PresBox.minimizedWidth : undefined }}>
                 {this.topPanel}
                 {this.toolbar}
                 {this.newDocumentToolbarDropdown}
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 2279ffe84..1f40b82a3 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -322,11 +322,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     };
 
     removeAllRecordingInOverlay = () => {
-        DocListCast(Doc.MyOverlayDocs.data).forEach(doc => {
-            if (doc.slides === this.rootDoc) {
-                Doc.RemoveDocFromList(Doc.MyOverlayDocs, undefined, doc);
-            }
-        });
+        DocListCast(Doc.MyOverlayDocs.data).filter(doc => doc.slides === this.rootDoc).forEach(Doc.RemFromMyOverlay);
     };
 
     static removeEveryExistingRecordingInOverlay = () => {
@@ -334,9 +330,9 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         DocListCast(Doc.MyOverlayDocs.data).forEach(doc => {
             if (doc.slides !== null) {
                 // if it's a recording video, don't remove from overlay (user can lose data)
-                if (!PresElementBox.videoIsRecorded(DocCast(doc.slides))) return;
-
-                Doc.RemoveDocFromList(Doc.MyOverlayDocs, undefined, doc);
+                if (PresElementBox.videoIsRecorded(DocCast(doc.slides))) {
+                    Doc.RemFromMyOverlay(doc);
+                }
             }
         });
     };
@@ -356,7 +352,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         if (!iconClick) PresElementBox.removeEveryExistingRecordingInOverlay();
 
         if (activeItem.recording) {
-            Doc.AddDocToList(Doc.MyOverlayDocs, undefined, DocCast(activeItem.recording));
+            Doc.AddToMyOverlay(DocCast(activeItem.recording));
         }
     };
 
@@ -368,7 +364,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
             // if we already have an existing recording
             this.showRecording(activeItem, true);
             //   // if we already have an existing recording
-            //   Doc.AddDocToList(Doc.MyOverlayDocs, undefined, Cast(activeItem.recording, Doc, null));
+            //   Doc.AddToMyOverlay(Cast(activeItem.recording, Doc, null));
         } else {
             // Remove every recording that already exists in overlay view
             // this is a design decision to clear to focus in on the recoding mode
@@ -392,7 +388,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
             // make recording box appear in the bottom right corner of the screen
             recording.overlayX = window.innerWidth - recording[WidthSym]() - 20;
             recording.overlayY = window.innerHeight - recording[HeightSym]() - 20;
-            Doc.AddDocToList(Doc.MyOverlayDocs, undefined, recording);
+            Doc.AddToMyOverlay(recording);
         }
     };
 
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index d8447deb6..128cfffb2 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -211,6 +211,15 @@ export class Doc extends RefField {
     public static get MyTrails() {
         return DocCast(Doc.ActiveDashboard?.myTrails);
     }
+    public static IsInMyOverlay(doc:Doc) {
+        return DocListCast(Doc.MyOverlayDocs?.data).includes(doc);
+    }
+    public static AddToMyOverlay(doc:Doc) {
+        Doc.AddDocToList(Doc.MyOverlayDocs, undefined, doc);
+    }
+    public static RemFromMyOverlay(doc:Doc) {
+        Doc.RemoveDocFromList(Doc.MyOverlayDocs, undefined, doc)
+    }
     public static get MyOverlayDocs() {
         return DocCast(Doc.UserDoc().myOverlayDocs);
     }
-- 
cgit v1.2.3-70-g09d2


From 52dbbf15cc337b076234c6f8519da94e8e28d94d Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 17 May 2023 23:26:29 -0400
Subject: changed creationDate to author_date. fixed undo bug with webboxes.  
 fixed menu problems with Open New Embedding and moved some collection menu
 items to buttons/

---
 src/client/documents/Documents.ts                  | 10 ++---
 src/client/util/CurrentUserUtils.ts                |  8 ++--
 src/client/util/DragManager.ts                     |  4 +-
 src/client/util/SettingsManager.tsx                |  2 +-
 src/client/views/InkTranscription.tsx              |  4 +-
 src/client/views/PropertiesView.tsx                |  2 +-
 src/client/views/StyleProvider.tsx                 |  2 +-
 src/client/views/collections/CollectionMenu.tsx    | 10 ++---
 src/client/views/collections/TreeView.tsx          |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 46 ++++++++++------------
 .../collectionSchema/CollectionSchemaView.tsx      | 12 +++---
 src/client/views/nodes/DocumentView.tsx            | 30 +++++++-------
 src/client/views/nodes/WebBox.tsx                  |  6 +--
 src/client/views/nodes/button/FontIconBox.tsx      | 12 ++++--
 src/client/views/pdf/PDFViewer.tsx                 |  2 +-
 src/fields/documentSchemas.ts                      |  2 +-
 16 files changed, 76 insertions(+), 78 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 35747f746..fb842b14f 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -141,7 +141,7 @@ export class DocumentOptions {
     isSystem?: BOOLt = new BoolInfo('is this a system created/owned doc', true);
     type?: STRt = new StrInfo('type of document', true, Array.from(Object.keys(DocumentType)));
     title?: string;
-    creationDate?: DATEt = new DateInfo('date the document was created', true);
+    author_date?: DATEt = new DateInfo('date the document was created', true);
     _dropAction?: DROPt = new DAInfo("what should happen to this document when it's dropped somewhere else");
     allowOverlayDrop?: BOOLt = new BoolInfo('can documents be dropped onto this document without using dragging title bar or holding down embed key (ctrl)?', true);
     childDropAction?: DROPt = new DAInfo("what should happen to the source document when it's dropped onto a child of a collection ");
@@ -450,7 +450,7 @@ export namespace Docs {
                 DocumentType.IMG,
                 {
                     layout: { view: ImageBox, dataField: defaultDataKey },
-                    options: {freeform: '',},
+                    options: { freeform: '' },
                 },
             ],
             [
@@ -829,7 +829,7 @@ export namespace Docs {
             dataProps.isSystem = viewProps.isSystem;
             dataProps.isDataDoc = true;
             dataProps.author = Doc.CurrentUserEmail;
-            dataProps.creationDate = new DateField();
+            dataProps.author_date = new DateField();
             if (fieldKey) {
                 dataProps[`${fieldKey}_modificationDate`] = new DateField();
                 dataProps[fieldKey] = data;
@@ -996,7 +996,7 @@ export namespace Docs {
             I.author = Doc.CurrentUserEmail;
             I.rotation = 0;
             I.defaultDoubleClick = 'click';
-            I.creationDate = new DateField();
+            I.author_date = new DateField();
             I['acl-Public'] = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment;
             //I['acl-Override'] = SharingPermissions.Unset;
             I[Initializing] = false;
@@ -1035,7 +1035,7 @@ export namespace Docs {
         // }
 
         export function FreeformDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
-            const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _xPadding: 20, _yPadding: 20,  ...options, _type_collection: CollectionViewType.Freeform }, id);
+            const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _xPadding: 20, _yPadding: 20, ...options, _type_collection: CollectionViewType.Freeform }, id);
             documents.forEach(d => (d.embedContainer = inst));
             return inst;
         }
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 85ab66301..99c63fc9d 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -201,7 +201,7 @@ export class CurrentUserUtils {
             makeIconTemplate(DocumentType.AUDIO, "title", { iconTemplate:DocumentType.LABEL, backgroundColor: "lightgreen"}),
             makeIconTemplate(DocumentType.PDF,   "title", { iconTemplate:DocumentType.LABEL, backgroundColor: "pink"}),
             makeIconTemplate(DocumentType.WEB,   "title", { iconTemplate:DocumentType.LABEL, backgroundColor: "brown"}),
-            makeIconTemplate(DocumentType.RTF,   "text",  { iconTemplate:DocumentType.LABEL, _layout_showTitle: "creationDate"}),
+            makeIconTemplate(DocumentType.RTF,   "text",  { iconTemplate:DocumentType.LABEL, _layout_showTitle: "author_date"}),
             makeIconTemplate(DocumentType.IMG,   "data",  { iconTemplate:DocumentType.IMG,   _height: undefined}),
             makeIconTemplate(DocumentType.COL,   "icon",  { iconTemplate:DocumentType.IMG}),
             makeIconTemplate(DocumentType.COL,   "icon",  { iconTemplate:DocumentType.IMG}),
@@ -231,7 +231,7 @@ export class CurrentUserUtils {
                             marks: [{ type: "strong" }]
                         }, {
                             type: "dashField",
-                            attrs: { fieldKey: "creationDate", docId: "", hideKey: false },
+                            attrs: { fieldKey: "author_date", docId: "", hideKey: false },
                             marks: [{ type: "strong" }]
                         }]
                     }]
@@ -628,8 +628,8 @@ export class CurrentUserUtils {
             { title: "Grid",          icon: "border-all",   toolTip: "Show Grid",           btnType: ButtonType.ToggleButton, expertMode: false, toolType:"grid",       funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
             { title: "View\xA0All",   icon: "object-group", toolTip: "Fit all Docs to View",btnType: ButtonType.ToggleButton, expertMode: false, toolType:"viewAll",    funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
             { title: "Clusters",      icon: "braille",      toolTip: "Show Doc Clusters",   btnType: ButtonType.ToggleButton, expertMode: false, toolType:"clusters",   funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
-            { title: "Arrange",icon: "arrow-down-short-wide",toolTip: "Toggle Auto Arrange",btnType: ButtonType.ToggleButton, expertMode: false, toolType:"arrange",    funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
-            { title: "Reset",         icon: "check",        toolTip: "Reset View",          btnType: ButtonType.ClickButton,  expertMode: false, backgroundColor:"transparent",    scripts: { onClick: 'resetView()'}}, // Only when floating document is selected in freeform
+            { title: "Cards",         icon: "brain",        toolTip: "Flashcards",          btnType: ButtonType.ToggleButton, expertMode: false, toolType:"flashcards", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
+            { title: "Arrange",icon:"arrow-down-short-wide",toolTip:"Toggle Auto Arrange",  btnType: ButtonType.ToggleButton, expertMode: false, toolType:"arrange",    funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
         ]
     }
     static textTools():Button[] {
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index e3798233e..c8bc26b93 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -196,7 +196,7 @@ export namespace DragManager {
     // drag a document and drop it (or make an embed/copy on drop)
     export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions, dropEvent?: () => any) {
         const addAudioTag = (dropDoc: any) => {
-            dropDoc && !dropDoc.creationDate && (dropDoc.creationDate = new DateField());
+            dropDoc && !dropDoc.author_date && (dropDoc.author_date = new DateField());
             dropDoc instanceof Doc && DocUtils.MakeLinkToActiveAudio(() => dropDoc);
             return dropDoc;
         };
@@ -325,7 +325,7 @@ export namespace DragManager {
     export let docsBeingDragged: Doc[] = observable([] as Doc[]);
     export let CanEmbed = false;
     export let DocDragData: DocumentDragData | undefined;
-    export function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void, dragUndoName?:string) {
+    export function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void, dragUndoName?: string) {
         if (dragData.dropAction === 'none') return;
         DocDragData = dragData as DocumentDragData;
         const batch = UndoManager.StartBatch(dragUndoName ?? 'document drag');
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index a3a924b75..f886ce2ca 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -176,7 +176,7 @@ export class SettingsManager extends React.Component<{}> {
         return (
             <div className="prefs-content">
                 <div>
-                    <input type="checkbox" onChange={e => (Doc.UserDoc().layout_showTitle = Doc.UserDoc().layout_showTitle ? undefined : 'creationDate')} checked={Doc.UserDoc().layout_showTitle !== undefined} />
+                    <input type="checkbox" onChange={e => (Doc.UserDoc().layout_showTitle = Doc.UserDoc().layout_showTitle ? undefined : 'author_date')} checked={Doc.UserDoc().layout_showTitle !== undefined} />
                     <div className="preferences-check">Show doc header</div>
                 </div>
                 <div>
diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx
index d6a6a64bf..025538160 100644
--- a/src/client/views/InkTranscription.tsx
+++ b/src/client/views/InkTranscription.tsx
@@ -128,7 +128,7 @@ export class InkTranscription extends React.Component {
                 const d = Cast(i.data, InkField, null);
                 const inkStroke = DocumentManager.Instance.getDocumentView(i)?.ComponentView as InkingStroke;
                 strokes.push(d.inkData.map(pd => inkStroke.ptToScreen({ X: pd.X, Y: pd.Y })));
-                times.push(DateCast(i.creationDate).getDate().getTime());
+                times.push(DateCast(i.author_date).getDate().getTime());
             });
 
         this.currGroup = groupDoc;
@@ -221,7 +221,7 @@ export class InkTranscription extends React.Component {
                         const docList = DocListCast(this.currGroup.data);
                         docList.forEach((inkDoc: Doc) => {
                             // just having the times match up and be a unique value (actual timestamp doesn't matter)
-                            const ms = DateCast(inkDoc.creationDate).getDate().getTime() + 14400000;
+                            const ms = DateCast(inkDoc.author_date).getDate().getTime() + 14400000;
                             const word = timestampWord.get(ms);
                             if (!word) {
                                 return;
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 0c89f1a27..5c3ed39a4 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -199,7 +199,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     }
 
     @computed get noviceFields() {
-        const noviceReqFields = ['author', 'creationDate', 'tags', '_layout_curPage'];
+        const noviceReqFields = ['author', 'author_date', 'tags', '_layout_curPage'];
         return this.editableFields(key => key.indexOf('modificationDate') !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith('acl')), noviceReqFields);
     }
 
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 28c61a11e..5efecaf3a 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -84,7 +84,7 @@ export function wavyBorderPath(pw: number, ph: number, inset: number = 0.05) {
 // a preliminary implementation of a dash style sheet for setting rendering properties of documents nested within a Tab
 //
 export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any {
-    const remoteDocHeader = 'author;creationDate;noMargin';
+    const remoteDocHeader = 'author;author_date;noMargin';
     const docProps = testDocProps(props) ? props : undefined;
     const selected = property.includes(':selected');
     const isCaption = property.includes(':caption');
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index d2338d742..6dd76465e 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -1004,14 +1004,12 @@ export class CollectionStackingViewChrome extends React.Component<CollectionView
 
         if (Doc.noviceMode) {
             if (docs instanceof Doc) {
-                const keys = Object.keys(docs).filter(key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('creationDate') >= 0 || key.indexOf('modificationDate') >= 0 || (key[0].toUpperCase() === key[0] && key[0] !== '_'));
+                const keys = Object.keys(docs).filter(key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('author_date') >= 0 || key.indexOf('modificationDate') >= 0 || (key[0].toUpperCase() === key[0] && key[0] !== '_'));
                 return keys.filter(key => key.toLowerCase().indexOf(val) > -1);
             }
             const keys = new Set<string>();
             docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
-            const noviceKeys = Array.from(keys).filter(
-                key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('creationDate') >= 0 || key.indexOf('modificationDate') >= 0 || (key[0]?.toUpperCase() === key[0] && key[0] !== '_')
-            );
+            const noviceKeys = Array.from(keys).filter(key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('author_date') >= 0 || key.indexOf('modificationDate') >= 0 || (key[0]?.toUpperCase() === key[0] && key[0] !== '_'));
             return noviceKeys.filter(key => key.toLowerCase().indexOf(val) > -1);
         }
 
@@ -1123,13 +1121,13 @@ export class CollectionNoteTakingViewChrome extends React.Component<CollectionVi
 
         if (Doc.UserDoc().noviceMode) {
             if (docs instanceof Doc) {
-                const keys = Object.keys(docs).filter(key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('creationDate') >= 0 || key.indexOf('modificationDate') >= 0 || (key[0].toUpperCase() === key[0] && key[0] !== '_'));
+                const keys = Object.keys(docs).filter(key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('author_date') >= 0 || key.indexOf('modificationDate') >= 0 || (key[0].toUpperCase() === key[0] && key[0] !== '_'));
                 return keys.filter(key => key.toLowerCase().indexOf(val) > -1);
             } else {
                 const keys = new Set<string>();
                 docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
                 const noviceKeys = Array.from(keys).filter(
-                    key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('creationDate') >= 0 || key.indexOf('modificationDate') >= 0 || (key[0]?.toUpperCase() === key[0] && key[0] !== '_')
+                    key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('author_date') >= 0 || key.indexOf('modificationDate') >= 0 || (key[0]?.toUpperCase() === key[0] && key[0] !== '_')
                 );
                 return noviceKeys.filter(key => key.toLowerCase().indexOf(val) > -1);
             }
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index abbb0df4c..f5877fa1a 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -761,7 +761,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         const makeFolder = { script: ScriptField.MakeFunction(`scriptContext.makeFolder()`, { scriptContext: 'any' })!, icon: 'folder-plus', label: 'New Folder' };
         const deleteItem = { script: ScriptField.MakeFunction(`scriptContext.deleteItem()`, { scriptContext: 'any' })!, icon: 'folder-plus', label: 'Delete' };
         const folderOp = this.childDocs?.length ? [makeFolder] : [];
-        const openEmbedding = { script: ScriptField.MakeFunction(`openDoc(getEmbedding(self), ${OpenWhere.addRight})`)!, icon: 'copy', label: 'Open New Embedding' };
+        const openEmbedding = { script: ScriptField.MakeFunction(`openDoc(getEmbedding(self), "${OpenWhere.addRight}")`)!, icon: 'copy', label: 'Open New Embedding' };
         const focusDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, icon: 'eye', label: 'Focus or Open' };
         return [
             ...(this.props.contextMenuItems ?? []).filter(mi => (!mi.filter ? true : mi.filter.script.run({ doc: this.doc })?.result)),
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 3b5dd45d9..8b9698293 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -872,9 +872,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 return true;
             }
             // pan the view if this is a regular collection, or it's an overlay and the overlay is zoomed (otherwise, there's nothing to pan)
-            if (!this.props.isAnnotationOverlay || (1 - NumCast(this.rootDoc._freeform_scale_min, 1)/ this.getLocalTransform().inverse().Scale)) {
-                this.pan(e);                
-                e.stopPropagation();  // if we are actually panning, stop propagation -- this will preven things like the overlayView from dragging the document while we're panning
+            if (!this.props.isAnnotationOverlay || 1 - NumCast(this.rootDoc._freeform_scale_min, 1) / this.getLocalTransform().inverse().Scale) {
+                this.pan(e);
+                e.stopPropagation(); // if we are actually panning, stop propagation -- this will preven things like the overlayView from dragging the document while we're panning
             }
         }
         return false;
@@ -1045,24 +1045,24 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     };
 
     @action
-    onPointerWheel = (e:React.WheelEvent): void => {
+    onPointerWheel = (e: React.WheelEvent): void => {
         if (this.Document._isGroup || !this.isContentActive()) return; // group style collections neither pan nor zoom
         PresBox.Instance?.pauseAutoPres();
         if (this.layoutDoc._Transform || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return;
         e.stopPropagation();
-        const docHeight = NumCast(this.rootDoc[Doc.LayoutFieldKey(this.rootDoc)+"_nativeHeight"], this.nativeHeight);
-        const scrollable = NumCast(this.layoutDoc[this.scaleFieldKey],1) ===1 && docHeight > this.props.PanelHeight() / this.nativeDimScaling ;
+        const docHeight = NumCast(this.rootDoc[Doc.LayoutFieldKey(this.rootDoc) + '_nativeHeight'], this.nativeHeight);
+        const scrollable = NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this.props.PanelHeight() / this.nativeDimScaling;
         switch (!e.ctrlKey ? Doc.UserDoc().freeformScrollMode : freeformScrollMode.Pan) {
             case freeformScrollMode.Pan:
                 // if ctrl is selected then zoom
                 if (!e.ctrlKey && this.props.isContentActive(true)) {
-                    this.scrollPan({ deltaX: -e.deltaX, deltaY: e.shiftKey ? 0 :-Math.max(-1, Math.min(1,e.deltaY)) });
+                    this.scrollPan({ deltaX: -e.deltaX, deltaY: e.shiftKey ? 0 : -Math.max(-1, Math.min(1, e.deltaY)) });
                     break;
                 }
             default:
             case freeformScrollMode.Zoom:
                 if ((e.ctrlKey || !scrollable) && this.props.isContentActive(true)) {
-                    this.zoom(e.clientX, e.clientY,Math.max(-1, Math.min(1,e.deltaY))); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
+                    this.zoom(e.clientX, e.clientY, Math.max(-1, Math.min(1, e.deltaY))); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
                     e.preventDefault();
                 }
                 break;
@@ -1109,24 +1109,24 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         if (!this.layoutDoc._lockedTransform || LightboxView.LightboxDoc) {
             this.setPanZoomTransition(panTime);
             const minScale = NumCast(this.rootDoc._freeform_scale_min, 1);
-            const scale = (1 - minScale/ this.getLocalTransform().inverse().Scale);
+            const scale = 1 - minScale / this.getLocalTransform().inverse().Scale;
             const minPanX = NumCast(this.rootDoc._freeform_panX_min, 0);
             const minPanY = NumCast(this.rootDoc._freeform_panY_min, 0);
             const maxPanX = NumCast(this.rootDoc._freeform_panX_max, this.nativeWidth);
-            const newPanX = Math.min(minPanX + scale *maxPanX, Math.max(minPanX, panX));
+            const newPanX = Math.min(minPanX + scale * maxPanX, Math.max(minPanX, panX));
             const fitYscroll = (((this.nativeHeight / this.nativeWidth) * this.props.PanelWidth() - this.props.PanelHeight()) * this.props.ScreenToLocalTransform().Scale) / minScale;
             const nativeHeight = (this.props.PanelHeight() / this.props.PanelWidth() / (this.nativeHeight / this.nativeWidth)) * this.nativeHeight;
             const maxScrollTop = this.nativeHeight / this.props.ScreenToLocalTransform().Scale - this.props.PanelHeight();
             const maxPanY =
                 minPanY + // minPanY + scrolling introduced by view scaling + scrolling introduced by layout_fitWidth
-                scale* NumCast(this.rootDoc._panY_max, nativeHeight) +
+                scale * NumCast(this.rootDoc._panY_max, nativeHeight) +
                 (!this.props.getScrollHeight?.() ? fitYscroll : 0); // when not zoomed, scrolling is handled via a scrollbar, not panning
             let newPanY = Math.max(minPanY, Math.min(maxPanY, panY));
             if (false && NumCast(this.rootDoc.layout_scrollTop) && NumCast(this.rootDoc._freeform_scale, minScale) !== minScale) {
                 const relTop = NumCast(this.rootDoc.layout_scrollTop) / maxScrollTop;
                 this.rootDoc.layout_scrollTop = undefined;
                 newPanY = minPanY + relTop * (maxPanY - minPanY);
-            } else if ( fitYscroll >2 && this.rootDoc.layout_scrollTop === undefined && NumCast(this.rootDoc._freeform_scale, minScale) === minScale) {
+            } else if (fitYscroll > 2 && this.rootDoc.layout_scrollTop === undefined && NumCast(this.rootDoc._freeform_scale, minScale) === minScale) {
                 const maxPanY = minPanY + fitYscroll;
                 const relTop = (panY - minPanY) / (maxPanY - minPanY);
                 setTimeout(() => (this.rootDoc.layout_scrollTop = relTop * maxScrollTop), 10);
@@ -1756,12 +1756,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             return;
         }
         !Doc.noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: 'Reset default note style', event: () => (Doc.UserDoc().defaultTextLayout = undefined), icon: 'eye' });
-        appearanceItems.push({ description: (Doc.UserDoc().defaultToFlashcards ? 'Disable' : 'Enable') + ' Flashcard Notes', event: () => (Doc.UserDoc().defaultToFlashcards = !Doc.UserDoc().defaultToFlashcards), icon: 'eye' });
-        appearanceItems.push({
-            description: `${this.fitContentsToBox ? 'Make Zoomable' : 'Scale to Window'}`,
-            event: () => (this.Document._freeform_fitContentsToBox = !this.fitContentsToBox),
-            icon: !this.fitContentsToBox ? 'expand-arrows-alt' : 'compress-arrows-alt',
-        });
         appearanceItems.push({ description: `Pin View`, event: () => TabDocView.PinDoc(this.rootDoc, { pinViewport: MarqueeView.CurViewBounds(this.rootDoc, this.props.PanelWidth(), this.props.PanelHeight()) }), icon: 'map-pin' });
         !Doc.noviceMode && appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: 'compress-arrows-alt' });
         this.props.renderDepth && appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' });
@@ -1966,12 +1960,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     lightboxPanelWidth = () => Math.max(0, this.props.PanelWidth() - 30);
     lightboxPanelHeight = () => Math.max(0, this.props.PanelHeight() - 30);
     lightboxScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-15, -15);
-    onPassiveWheel = (e:WheelEvent) => {
-        const docHeight = NumCast(this.rootDoc[Doc.LayoutFieldKey(this.rootDoc)+"_nativeHeight"], this.nativeHeight);
-        const scrollable = NumCast(this.layoutDoc[this.scaleFieldKey],1) ===1 && docHeight > this.props.PanelHeight() / this.nativeDimScaling ;
-        this.props.isSelected() && !scrollable && e.preventDefault()
-    }
-    _oldWheel:any;
+    onPassiveWheel = (e: WheelEvent) => {
+        const docHeight = NumCast(this.rootDoc[Doc.LayoutFieldKey(this.rootDoc) + '_nativeHeight'], this.nativeHeight);
+        const scrollable = NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this.props.PanelHeight() / this.nativeDimScaling;
+        this.props.isSelected() && !scrollable && e.preventDefault();
+    };
+    _oldWheel: any;
     render() {
         TraceMobx();
         return (
@@ -1980,9 +1974,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 ref={r => {
                     this.createDashEventsTarget(r);
                     this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
-                    this._oldWheel= r;
+                    this._oldWheel = r;
                     // prevent wheel events from passivly propagating up through containers
-                     r?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
+                    r?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
                 }}
                 onWheel={this.onPointerWheel}
                 onClick={this.onClick}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 1cc48bebe..5962afa6b 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -47,7 +47,7 @@ export const FInfotoColType: { [key: string]: ColumnType } = {
     rtf: ColumnType.RTF,
 };
 
-const defaultColumnKeys: string[] = ['title', 'type', 'author', 'creationDate', 'text'];
+const defaultColumnKeys: string[] = ['title', 'type', 'author', 'author_date', 'text'];
 
 @observer
 export class CollectionSchemaView extends CollectionSubView() {
@@ -686,12 +686,12 @@ export class CollectionSchemaView extends CollectionSubView() {
         );
     }
 
-    onPassiveWheel = (e:WheelEvent) => { 
+    onPassiveWheel = (e: WheelEvent) => {
         // 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._oldWheel.scrollTop && e.deltaY <= 0) e.preventDefault();
         e.stopPropagation();
-    }
-    _oldWheel:any;
+    };
+    _oldWheel: any;
     @computed get keysDropdown() {
         return (
             <div className="schema-key-search">
@@ -707,8 +707,8 @@ export class CollectionSchemaView extends CollectionSubView() {
                     className="schema-key-list"
                     ref={r => {
                         this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
-                        this._oldWheel= r;
-                        r?.addEventListener('wheel', this.onPassiveWheel, { passive: false } );
+                        this._oldWheel = r;
+                        r?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
                     }}>
                     {this._menuKeys.map(key => (
                         <div
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 84f82a4b9..ab285b230 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -734,18 +734,20 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 onClicks.push({ description: 'Enter Portal', event: this.makeIntoPortal, icon: 'window-restore' });
                 !Doc.noviceMode && onClicks.push({ description: 'Toggle Detail', event: this.setToggleDetail, icon: 'concierge-bell' });
 
-                if (!this.Document.annotationOn) {
-                    const options = cm.findByDescription('Options...');
-                    const optionItems: ContextMenuProps[] = options && 'subitems' in options ? options.subitems : [];
-                    !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'compass' });
-
-                    onClicks.push({ description: this.onClickHandler ? 'Remove Click Behavior' : 'Follow Link', event: () => this.toggleFollowLink(false, false), icon: 'link' });
-                    onClicks.push({ description: 'Edit onClick Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, 'onClick'), 'edit onClick'), icon: 'terminal' });
-                    !existingOnClick && cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
-                } else if (LinkManager.Links(this.Document).length) {
-                    onClicks.push({ description: 'Select on Click', event: () => this.noOnClick(), icon: 'link' });
-                    onClicks.push({ description: 'Follow Link on Click', event: () => this.followLinkOnClick(), icon: 'link' });
-                    !existingOnClick && cm.addItem({ description: 'OnClick...', subitems: onClicks, icon: 'mouse-pointer' });
+                if (!this.props.treeViewDoc) {
+                    if (!this.Document.annotationOn) {
+                        const options = cm.findByDescription('Options...');
+                        const optionItems: ContextMenuProps[] = options && 'subitems' in options ? options.subitems : [];
+                        !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'compass' });
+
+                        onClicks.push({ description: this.onClickHandler ? 'Remove Click Behavior' : 'Follow Link', event: () => this.toggleFollowLink(false, false), icon: 'link' });
+                        onClicks.push({ description: 'Edit onClick Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, 'onClick'), 'edit onClick'), icon: 'terminal' });
+                        !existingOnClick && cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
+                    } else if (LinkManager.Links(this.Document).length) {
+                        onClicks.push({ description: 'Select on Click', event: () => this.noOnClick(), icon: 'link' });
+                        onClicks.push({ description: 'Follow Link on Click', event: () => this.followLinkOnClick(), icon: 'link' });
+                        !existingOnClick && cm.addItem({ description: 'OnClick...', subitems: onClicks, icon: 'mouse-pointer' });
+                    }
                 }
             }
 
@@ -827,7 +829,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 break;
         }
         // Add link to help documentation
-        if (documentationDescription && documentationLink) {
+        if (!this.props.treeViewDoc && documentationDescription && documentationLink) {
             helpItems.push({
                 description: documentationDescription,
                 event: () => window.open(documentationLink, '_blank'),
@@ -1116,7 +1118,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                             if (this.props.layout_showTitle) {
                                 this.rootDoc._layout_showTitle = input?.substring(1) ? input.substring(1) : undefined;
                             } else {
-                                Doc.UserDoc().layout_showTitle = input?.substring(1) ? input.substring(1) : 'creationDate';
+                                Doc.UserDoc().layout_showTitle = input?.substring(1) ? input.substring(1) : 'author_date';
                             }
                         } else {
                             var value = input.replace(new RegExp(layout_showTitle + '='), '') as string | number;
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index be70e6262..a898398a1 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -479,7 +479,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         }
                         const origin = this.webField?.origin;
                         if (href && origin) {
-                        const batch = UndoManager.StartBatch('webclick');
+                            const batch = UndoManager.StartBatch('webclick');
                             e.stopPropagation();
                             setTimeout(() => {
                                 this.setData(href.replace(Utils.prepend(''), origin));
@@ -510,7 +510,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             if (this._innerCollectionView) {
                 this._innerCollectionView.zoom(e.screenX, e.screenY, e.deltaY);
                 const offset = e.clientY - NumCast(this.layoutDoc._layout_scrollTop);
-                this.layoutDoc.panY = offset - offset / NumCast(this.layoutDoc._freeform_scale) + NumCast(this.layoutDoc._layout_scrollTop) - NumCast(this.layoutDoc._layout_scrollTop) / NumCast(this.layoutDoc._freeform_scale);
+                this.layoutDoc.freeform_panY = offset - offset / NumCast(this.layoutDoc._freeform_scale) + NumCast(this.layoutDoc._layout_scrollTop) - NumCast(this.layoutDoc._layout_scrollTop) / NumCast(this.layoutDoc._freeform_scale);
             }
             e.preventDefault();
         }
@@ -745,7 +745,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     ref={action((r: HTMLIFrameElement | null) => (this._iframe = r))}
                     src={url}
                     onLoad={this.iframeLoaded}
-                    scrolling="no" // ugh.. on windows, I get an inner scroll bar for the iframe's body even though the scrollHeight should be set to the full height of the document.  
+                    scrolling="no" // ugh.. on windows, I get an inner scroll bar for the iframe's body even though the scrollHeight should be set to the full height of the document.
                     // the 'allow-top-navigation' and 'allow-top-navigation-by-user-activation' attributes are left out to prevent iframes from redirecting the top-level Dash page
                     // sandbox={"allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin allow-scripts"} />;
                     sandbox={`${this.layoutDoc.allowScripts ? 'allow-scripts' : ''} allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin`}
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index b07cf7e00..fb29f95f4 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -615,7 +615,7 @@ ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boole
     }
     Doc.SharingDoc().userColor = undefined;
     Doc.GetProto(Doc.SharingDoc()).userColor = color;
-    Doc.UserDoc().layout_showTitle = color === 'transparent' ? undefined : StrCast(Doc.UserDoc().layout_showTitle, 'creationDate');
+    Doc.UserDoc().layout_showTitle = color === 'transparent' ? undefined : StrCast(Doc.UserDoc().layout_showTitle, 'author_date');
 });
 
 // toggle: Set overlay status of selected document
@@ -628,10 +628,10 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
     selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log('[FontIconBox.tsx] toggleOverlay failed');
 });
 
-ScriptingGlobals.add(function showFreeform(attr: 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll', checkResult?: boolean) {
+ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll', checkResult?: boolean) {
     const selected = SelectionManager.Docs().lastElement();
     // prettier-ignore
-    const map: Map<'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc) => void;}> = new Map([
+    const map: Map<'flashcards' | 'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc) => void;}> = new Map([
         ['grid', {
             checkResult: (doc:Doc) => doc._freeform_backgroundGrid,
             setDoc: (doc:Doc) => doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid,
@@ -654,7 +654,11 @@ ScriptingGlobals.add(function showFreeform(attr: 'grid' | 'snaplines' | 'cluster
             checkResult: (doc:Doc) => doc._autoArrange,
             setDoc: (doc:Doc) => doc._autoArrange = !doc._autoArrange,
         }],
-    ]);
+        ['flashcards', {
+            checkResult: (doc:Doc) => Doc.UserDoc().defaultToFlashcards,
+            setDoc: (doc:Doc) => Doc.UserDoc().defaultToFlashcards = !Doc.UserDoc().defaultToFlashcards,
+        }],
+   ]);
 
     if (checkResult) {
         return map.get(attr)?.checkResult(selected) ? Colors.MEDIUM_BLUE : 'transparent';
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index ac43b2956..999e3eeb6 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -500,7 +500,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
         return !this._overlayAnnoInfo ? null : (
             <div className="pdfViewerDash-overlayAnno" style={{ top: NumCast(this._overlayAnnoInfo.y), left: NumCast(this._overlayAnnoInfo.x) }}>
                 <div className="pdfViewerDash-overlayAnno" style={{ right: -50, background: SharingManager.Instance.users.find(users => users.user.email === this._overlayAnnoInfo!.author)?.userColor }}>
-                    {this._overlayAnnoInfo.author + ' ' + Field.toString(this._overlayAnnoInfo.creationDate as Field)}
+                    {this._overlayAnnoInfo.author + ' ' + Field.toString(this._overlayAnnoInfo.author_date as Field)}
                 </div>
             </div>
         );
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index 7b2fd74d0..a5361b98a 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -9,7 +9,7 @@ export const documentSchema = createSchema({
     type: 'string', // enumerated type of document -- should be template-specific (ie, start with an '_')
     title: 'string', // document title (can be on either data document or layout)
     isTemplateForField: 'string', // if specified, it indicates the document is a template that renders the specified field
-    creationDate: DateField, // when the document was created
+    author_date: DateField, // when the document was created
     links: listSpec(Doc), // computed (readonly) list of links associated with this document
 
     // "Location" properties in a very general sense
-- 
cgit v1.2.3-70-g09d2


From 562e965cbc3d7629014ad3902e1177d5cbefd57c Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 6 Jun 2023 18:48:59 -0400
Subject: updated Symbol naming.  fixed various compile errors related to type
 checking.

---
 src/client/documents/Documents.ts                  |   3 +-
 src/client/util/DictationManager.ts                |   4 +-
 src/client/util/DocumentManager.ts                 |   3 +-
 src/client/util/LinkManager.ts                     |  17 +-
 src/client/util/SharingManager.tsx                 |  23 ++-
 src/client/views/DocComponent.tsx                  |  14 +-
 src/client/views/DocumentDecorations.tsx           |   9 +-
 src/client/views/InkTranscription.tsx              |  11 +-
 src/client/views/InkingStroke.tsx                  |  11 +-
 src/client/views/MainView.tsx                      |   4 +-
 src/client/views/OverlayView.tsx                   |   9 +-
 src/client/views/PropertiesView.tsx                |  29 +--
 src/client/views/collections/CollectionMenu.tsx    |   4 +-
 .../views/collections/CollectionNoteTakingView.tsx |  11 +-
 .../views/collections/CollectionPileView.tsx       |  19 +-
 .../views/collections/CollectionStackingView.tsx   |  13 +-
 .../views/collections/CollectionTreeView.tsx       |  19 +-
 src/client/views/collections/TabDocView.tsx        |  11 +-
 src/client/views/collections/TreeView.tsx          |  15 +-
 .../CollectionFreeFormLayoutEngines.tsx            |  11 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  25 +--
 .../collections/collectionFreeForm/MarqueeView.tsx |   5 +-
 .../collectionLinear/CollectionLinearView.tsx      |  17 +-
 .../collectionSchema/SchemaTableCell.tsx           |   4 +-
 src/client/views/nodes/ColorBox.tsx                |   5 +-
 src/client/views/nodes/DocumentView.tsx            |  17 +-
 src/client/views/nodes/EquationBox.tsx             |   6 +-
 src/client/views/nodes/ImageBox.tsx                |  11 +-
 src/client/views/nodes/KeyValueBox.tsx             |   2 +-
 src/client/views/nodes/KeyValuePair.tsx            |   7 +-
 src/client/views/nodes/LinkDocPreview.tsx          |  17 +-
 src/client/views/nodes/MapBox/MapBox2.tsx          |   5 +-
 src/client/views/nodes/PDFBox.tsx                  |  27 +--
 .../views/nodes/RecordingBox/RecordingBox.tsx      |   3 +-
 src/client/views/nodes/ScreenshotBox.tsx           |  12 +-
 src/client/views/nodes/VideoBox.tsx                |  17 +-
 src/client/views/nodes/WebBox.tsx                  |  25 +--
 src/client/views/nodes/button/FontIconBox.tsx      |  11 +-
 .../views/nodes/formattedText/DashDocView.tsx      |   9 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  29 +--
 src/client/views/nodes/trails/PresElementBox.tsx   |  13 +-
 src/client/views/pdf/PDFViewer.tsx                 |   5 +-
 src/fields/Doc.ts                                  | 228 ++++++++++-----------
 src/fields/DocSymbols.ts                           |  26 +++
 src/fields/FieldSymbols.ts                         |  21 +-
 src/fields/List.ts                                 |  92 ++++-----
 src/fields/util.ts                                 |  80 +++-----
 47 files changed, 496 insertions(+), 463 deletions(-)
 create mode 100644 src/fields/DocSymbols.ts

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 515a870b5..934b9e307 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -2,7 +2,8 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { action, runInAction } from 'mobx';
 import { basename } from 'path';
 import { DateField } from '../../fields/DateField';
-import { Doc, DocListCast, Field, Initializing, Opt, updateCachedAcls } from '../../fields/Doc';
+import { Doc, DocListCast, Field, Opt, updateCachedAcls } from '../../fields/Doc';
+import { Initializing } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
 import { HtmlField } from '../../fields/HtmlField';
 import { InkField, PointData } from '../../fields/InkField';
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index 6c710728b..717473aa1 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -5,7 +5,7 @@ import { Doc, Opt } from '../../fields/Doc';
 import { List } from '../../fields/List';
 import { RichTextField } from '../../fields/RichTextField';
 import { listSpec } from '../../fields/Schema';
-import { Cast, CastCtor } from '../../fields/Types';
+import { Cast, CastCtor, DocCast } from '../../fields/Types';
 import { AudioField, ImageField } from '../../fields/URLField';
 import { Utils } from '../../Utils';
 import { Docs } from '../documents/Documents';
@@ -328,7 +328,7 @@ export namespace DictationManager {
                 {
                     action: (target: DocumentView) => {
                         const newBox = Docs.Create.TextDocument('', { _width: 400, _height: 200, title: 'My Outline', _layout_autoHeight: true });
-                        const proto = newBox.proto!;
+                        const proto = DocCast(newBox.proto);
                         const prompt = 'Press alt + r to start dictating here...';
                         const head = 3;
                         const anchor = head + prompt.length;
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 642ea26da..1f8f4fd45 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -115,8 +115,7 @@ export class DocumentManager {
         });
         if (toReturn.length === 0) {
             DocumentManager.Instance.DocumentViews.forEach(view => {
-                const doc = view.rootDoc.proto;
-                if (doc && doc[Id] && doc[Id] === id) {
+                if (Doc.GetProto(view.rootDoc)?.[Id] === id) {
                     toReturn.push(view);
                 }
             });
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 5e6107e5f..41c2fa889 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -1,6 +1,7 @@
 import { action, observable, observe } from 'mobx';
 import { computedFn } from 'mobx-utils';
-import { DirectLinksSym, Doc, DocListCast, DocListCastAsync, Field, Opt } from '../../fields/Doc';
+import { Doc, DocListCast, DocListCastAsync, Field, Opt } from '../../fields/Doc';
+import { DirectLinks } from '../../fields/DocSymbols';
 import { List } from '../../fields/List';
 import { ProxyField } from '../../fields/Proxy';
 import { Cast, DocCast, PromiseValue, StrCast } from '../../fields/Types';
@@ -55,8 +56,8 @@ export class LinkManager {
                             a2 &&
                             Promise.all([Doc.GetProto(a1), Doc.GetProto(a2)]).then(
                                 action(protos => {
-                                    (protos[0] as Doc)?.[DirectLinksSym].add(link);
-                                    (protos[1] as Doc)?.[DirectLinksSym].add(link);
+                                    (protos[0] as Doc)?.[DirectLinks].add(link);
+                                    (protos[1] as Doc)?.[DirectLinks].add(link);
                                 })
                             );
                     });
@@ -69,9 +70,9 @@ export class LinkManager {
             Promise.all([a1, a2]).then(
                 action(() => {
                     if (a1 instanceof Doc && a2 instanceof Doc && ((a1.author !== undefined && a2.author !== undefined) || link.author === Doc.CurrentUserEmail)) {
-                        Doc.GetProto(a1)[DirectLinksSym].delete(link);
-                        Doc.GetProto(a2)[DirectLinksSym].delete(link);
-                        Doc.GetProto(link)[DirectLinksSym].delete(link);
+                        Doc.GetProto(a1)[DirectLinks].delete(link);
+                        Doc.GetProto(a2)[DirectLinks].delete(link);
+                        Doc.GetProto(link)[DirectLinks].delete(link);
                     }
                 })
             );
@@ -153,7 +154,7 @@ export class LinkManager {
         return this.relatedLinker(anchor);
     } // finds all links that contain the given anchor
     public getAllDirectLinks(anchor: Doc): Doc[] {
-        return Array.from(Doc.GetProto(anchor)[DirectLinksSym] ?? []);
+        return Array.from(Doc.GetProto(anchor)[DirectLinks] ?? []);
     } // finds all links that contain the given anchor
 
     relatedLinker = computedFn(function relatedLinker(this: any, anchor: Doc): Doc[] {
@@ -161,7 +162,7 @@ export class LinkManager {
             console.log('WAITING FOR DOC/PROTO IN LINKMANAGER');
             return [];
         }
-        const dirLinks = Doc.GetProto(anchor)[DirectLinksSym];
+        const dirLinks = Doc.GetProto(anchor)[DirectLinks];
         const annos = DocListCast(anchor[Doc.LayoutFieldKey(anchor) + '_annotations']);
         if (!annos) debugger;
         return annos.reduce((list, anno) => [...list, ...LinkManager.Instance.relatedLinker(anno)], Array.from(dirLinks).slice());
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 0eecb31cf..97e64ab71 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -5,7 +5,8 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import Select from 'react-select';
 import * as RequestPromise from 'request-promise';
-import { AclAdmin, AclPrivate, AclSym, AclUnset, DataSym, Doc, DocListCast, DocListCastAsync, HierarchyMapping } from '../../fields/Doc';
+import { Doc, DocListCast, DocListCastAsync, HierarchyMapping } from '../../fields/Doc';
+import { AclAdmin, AclPrivate, DocAcl, AclUnset, DocData } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
 import { List } from '../../fields/List';
 import { NumCast, StrCast } from '../../fields/Types';
@@ -158,7 +159,7 @@ export class SharingManager extends React.Component<{}> {
 
         const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.props.Document);
         return !docs
-            .map(doc => (this.layoutDocAcls ? doc : doc[DataSym]))
+            .map(doc => (this.layoutDocAcls ? doc : doc[DocData]))
             .map(doc => {
                 doc.author === Doc.CurrentUserEmail && !doc[myAcl] && distributeAcls(myAcl, SharingPermissions.Admin, doc, undefined, undefined, isDashboard);
 
@@ -192,7 +193,7 @@ export class SharingManager extends React.Component<{}> {
 
         // ! ensures it returns true if document has been shared successfully, false otherwise
         return !docs
-            .map(doc => (this.layoutDocAcls ? doc : doc[DataSym]))
+            .map(doc => (this.layoutDocAcls ? doc : doc[DocData]))
             .map(doc => {
                 doc.author === Doc.CurrentUserEmail && !doc[`acl-${Doc.CurrentUserEmailNormalized}`] && distributeAcls(`acl-${Doc.CurrentUserEmailNormalized}`, SharingPermissions.Admin, doc, undefined, undefined, isDashboard);
 
@@ -273,7 +274,7 @@ export class SharingManager extends React.Component<{}> {
                 doc.isShared = true;
                 doc.backgroundColor = 'green';
             } else {
-                const acls = doc[DataSym][AclSym];
+                const acls = doc[DocData][DocAcl];
                 if (
                     Object.keys(acls)
                         .filter(key => key !== `acl-${Doc.CurrentUserEmailNormalized}` && key !== 'acl-Me')
@@ -485,7 +486,7 @@ export class SharingManager extends React.Component<{}> {
         const groups = this.groupSort === 'ascending' ? groupList.slice().sort(this.sortGroups) : this.groupSort === 'descending' ? groupList.slice().sort(this.sortGroups).reverse() : groupList;
 
         // handles the case where multiple documents are selected
-        let docs = SelectionManager.Views().length < 2 ? [this.layoutDocAcls ? this.targetDoc : this.targetDoc?.[DataSym]] : SelectionManager.Views().map(docView => (this.layoutDocAcls ? docView.props.Document : docView.props.Document?.[DataSym]));
+        let docs = SelectionManager.Views().length < 2 ? [this.layoutDocAcls ? this.targetDoc : this.targetDoc?.[DocData]] : SelectionManager.Views().map(docView => (this.layoutDocAcls ? docView.props.Document : docView.props.Document?.[DocData]));
 
         if (this.myDocAcls) {
             const newDocs: Doc[] = [];
@@ -493,21 +494,21 @@ export class SharingManager extends React.Component<{}> {
             docs = newDocs.filter(doc => GetEffectiveAcl(doc) === AclAdmin);
         }
 
-        const targetDoc = this.layoutDocAcls ? docs[0] : docs[0]?.[DataSym];
+        const targetDoc = this.layoutDocAcls ? docs[0] : docs[0]?.[DocData];
 
         // tslint:disable-next-line: no-unnecessary-callback-wrapper
         const effectiveAcls = docs.map(doc => GetEffectiveAcl(doc));
         const admin = this.myDocAcls ? Boolean(docs.length) : effectiveAcls.every(acl => acl === AclAdmin);
 
         // users in common between all docs
-        const commonKeys = intersection(...docs.map(doc => (this.layoutDocAcls ? doc : doc[DataSym])).map(doc => doc?.[AclSym] && Object.keys(doc[AclSym])));
+        const commonKeys = intersection(...docs.map(doc => (this.layoutDocAcls ? doc : doc[DocData])).map(doc => doc?.[DocAcl] && Object.keys(doc[DocAcl])));
 
         // the list of users shared with
         const userListContents: (JSX.Element | null)[] = users
             .filter(({ user }) => (docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(user.email)}`) : docs[0]?.author !== user.email))
             .map(({ user, linkDatabase, sharingDoc, userColor }) => {
                 const userKey = `acl-${normalizeEmail(user.email)}`;
-                const uniform = docs.map(doc => (this.layoutDocAcls ? doc : doc[DataSym])).every(doc => doc?.[AclSym]?.[userKey] === docs[0]?.[AclSym]?.[userKey]);
+                const uniform = docs.map(doc => (this.layoutDocAcls ? doc : doc[DocData])).every(doc => doc?.[DocAcl]?.[userKey] === docs[0]?.[DocAcl]?.[userKey]);
                 const permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-';
 
                 return !permissions ? null : (
@@ -533,7 +534,7 @@ export class SharingManager extends React.Component<{}> {
         userListContents.unshift(
             sameAuthor ? (
                 <div key={'owner'} className={'container'}>
-                    <span className={'padding'}>{targetDoc?.author === Doc.CurrentUserEmail ? 'Me' : targetDoc?.author}</span>
+                    <span className="padding">{targetDoc?.author === Doc.CurrentUserEmail ? 'Me' : StrCast(targetDoc?.author)}</span>
                     <div className="edit-actions">
                         <div className={'permissions-dropdown'}>Owner</div>
                     </div>
@@ -555,8 +556,8 @@ export class SharingManager extends React.Component<{}> {
         const groupListContents = groupListMap.map(group => {
             const groupKey = `acl-${StrCast(group.title)}`;
             const uniform = docs
-                .map(doc => (this.layoutDocAcls ? doc : doc[DataSym]))
-                .every(doc => (this.layoutDocAcls ? doc?.[AclSym]?.[groupKey] === docs[0]?.[AclSym]?.[groupKey] : doc?.[DataSym]?.[AclSym]?.[groupKey] === docs[0]?.[DataSym]?.[AclSym]?.[groupKey]));
+                .map(doc => (this.layoutDocAcls ? doc : doc[DocData]))
+                .every(doc => (this.layoutDocAcls ? doc?.[DocAcl]?.[groupKey] === docs[0]?.[DocAcl]?.[groupKey] : doc?.[DocData]?.[DocAcl]?.[groupKey] === docs[0]?.[DocData]?.[DocAcl]?.[groupKey]));
             const permissions = uniform ? StrCast(targetDoc?.[`acl-${StrCast(group.title)}`]) : '-multiple-';
 
             return !permissions ? null : (
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index ffb2d92f6..6fab4430d 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -1,7 +1,7 @@
 import { action, computed, observable } from 'mobx';
 import { DateField } from '../../fields/DateField';
-import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, AclSym, DataSym, Doc, DocListCast, Opt } from '../../fields/Doc';
-import { InkTool } from '../../fields/InkField';
+import { DocListCast, Opt, Doc } from '../../fields/Doc';
+import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, DocAcl, DocData } from '../../fields/DocSymbols';
 import { List } from '../../fields/List';
 import { Cast, ScriptCast } from '../../fields/Types';
 import { denormalizeEmail, distributeAcls, GetEffectiveAcl, inheritParentAcls, SharingPermissions } from '../../fields/util';
@@ -36,7 +36,7 @@ export function DocComponent<P extends DocComponentProps>() {
         }
         // This is the data part of a document -- ie, the data that is constant across all views of the document
         @computed get dataDoc() {
-            return this.props.Document[DataSym] as Doc;
+            return this.props.Document[DocData] as Doc;
         }
         // key where data is stored
         @computed get fieldKey() {
@@ -74,7 +74,7 @@ export function ViewBoxBaseComponent<P extends ViewBoxBaseProps>() {
         }
         // This is the data part of a document -- ie, the data that is constant across all views of the document
         @computed get dataDoc() {
-            return this.props.DataDoc && (this.props.Document.isTemplateForField || this.props.Document.isTemplateDoc) ? this.props.DataDoc : this.props.Document[DataSym];
+            return this.props.DataDoc && (this.props.Document.isTemplateForField || this.props.Document.isTemplateDoc) ? this.props.DataDoc : this.props.Document[DocData];
         }
         // key where data is stored
         @computed get fieldKey() {
@@ -118,7 +118,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
         }
         // This is the data part of a document -- ie, the data that is constant across all views of the document
         @computed get dataDoc() {
-            return this.props.DataDoc && (this.props.Document.isTemplateForField || this.props.Document.isTemplateDoc) ? this.props.DataDoc : this.props.Document[DataSym];
+            return this.props.DataDoc && (this.props.Document.isTemplateForField || this.props.Document.isTemplateDoc) ? this.props.DataDoc : this.props.Document[DocData];
         }
 
         // key where data is stored
@@ -185,7 +185,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
             if (this.props.filterAddDocument?.(docs) === false || docs.find(doc => Doc.AreProtosEqual(doc, this.props.Document) && Doc.LayoutField(doc) === Doc.LayoutField(this.props.Document))) {
                 return false;
             }
-            const targetDataDoc = this.props.Document[DataSym];
+            const targetDataDoc = this.props.Document[DocData];
             const effectiveAcl = GetEffectiveAcl(targetDataDoc);
 
             if (effectiveAcl === AclPrivate || effectiveAcl === AclReadonly) {
@@ -193,7 +193,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
             }
             const added = docs;
             if (added.length) {
-                const aclKeys = Object.keys(this.props.Document[AclSym] ?? {});
+                const aclKeys = Object.keys(this.props.Document[DocAcl] ?? {});
                 aclKeys.forEach(key =>
                     added.forEach(d => {
                         if (d.author === denormalizeEmail(key.substring(4)) && !d.createdFrom) {
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 72c6d449a..e4d50b143 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -6,7 +6,8 @@ import { action, computed, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import { FaUndo } from 'react-icons/fa';
 import { DateField } from '../../fields/DateField';
-import { AclAdmin, AclEdit, DataSym, Doc, DocListCast, Field, HeightSym, WidthSym } from '../../fields/Doc';
+import { Doc, DocListCast, Field } from '../../fields/Doc';
+import { AclAdmin, AclEdit, DocData, Height, Width } from '../../fields/DocSymbols';
 import { InkField } from '../../fields/InkField';
 import { RichTextField } from '../../fields/RichTextField';
 import { ScriptField } from '../../fields/ScriptField';
@@ -279,8 +280,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                 // open centered in a new workspace with Shift Key
                 const embedding = Doc.MakeEmbedding(selectedDocs[0].rootDoc);
                 embedding.embedContainer = undefined;
-                embedding.x = -embedding[WidthSym]() / 2;
-                embedding.y = -embedding[HeightSym]() / 2;
+                embedding.x = -embedding[Width]() / 2;
+                embedding.y = -embedding[Height]() / 2;
                 CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([embedding], { title: 'Tab for ' + embedding.title }), OpenWhereMod.right);
             } else if (e.altKey) {
                 // open same document in new tab
@@ -765,7 +766,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
             seldocview.props.hideDeleteButton ||
             seldocview.rootDoc.hideDeleteButton ||
             SelectionManager.Views().some(docView => {
-                const collectionAcl = docView.props.docViewPath()?.lastElement() ? GetEffectiveAcl(docView.props.docViewPath().lastElement().rootDoc[DataSym]) : AclEdit;
+                const collectionAcl = docView.props.docViewPath()?.lastElement() ? GetEffectiveAcl(docView.props.docViewPath().lastElement().rootDoc[DocData]) : AclEdit;
                 return (docView.rootDoc.stayInCollection && !docView.rootDoc.timelineLabel) || (collectionAcl !== AclAdmin && collectionAcl !== AclEdit && GetEffectiveAcl(docView.rootDoc) !== AclAdmin);
             });
 
diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx
index 025538160..e38474ea0 100644
--- a/src/client/views/InkTranscription.tsx
+++ b/src/client/views/InkTranscription.tsx
@@ -1,7 +1,8 @@
 import * as iink from 'iink-js';
 import { action, observable } from 'mobx';
 import * as React from 'react';
-import { Doc, DocListCast, HeightSym, WidthSym } from '../../fields/Doc';
+import { Doc, DocListCast } from '../../fields/Doc';
+import { Height, Width } from '../../fields/DocSymbols';
 import { InkData, InkField, InkTool } from '../../fields/InkField';
 import { Cast, DateCast, NumCast } from '../../fields/Types';
 import { aggregateBounds } from '../../Utils';
@@ -283,8 +284,8 @@ export class InkTranscription extends React.Component {
             action(d => {
                 const x = NumCast(d.x);
                 const y = NumCast(d.y);
-                const width = d[WidthSym]();
-                const height = d[HeightSym]();
+                const width = d[Width]();
+                const height = d[Height]();
                 bounds.push({ x, y, width, height });
             })
         );
@@ -323,8 +324,8 @@ export class InkTranscription extends React.Component {
         // Gets a collection based on the selected nodes using a marquee view ref
         const newCollection = marqViewRef?.getCollection(selected, undefined, true);
         if (newCollection) {
-            newCollection.height = newCollection[HeightSym]();
-            newCollection.width = newCollection[WidthSym]();
+            newCollection.height = newCollection[Height]();
+            newCollection.width = newCollection[Width]();
             // if the grouping we are creating is an individual word
             if (word) {
                 newCollection.title = word;
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 545e20ed1..91452fbf5 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -23,7 +23,8 @@
 import React = require('react');
 import { action, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
-import { Doc, HeightSym, WidthSym } from '../../fields/Doc';
+import { Doc } from '../../fields/Doc';
+import { Height, Width } from '../../fields/DocSymbols';
 import { InkData, InkField } from '../../fields/InkField';
 import { BoolCast, Cast, NumCast, RTFCast, StrCast } from '../../fields/Types';
 import { TraceMobx } from '../../fields/util';
@@ -370,10 +371,10 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
         const strokeColor = !closed && fillColor && fillColor !== 'transparent' ? fillColor : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color) ?? StrCast(this.layoutDoc.color);
 
         // bcz: Hack!! Not really sure why, but having fractional values for width/height of mask ink strokes causes the dragging clone (see DragManager) to be offset from where it should be.
-        if (isInkMask && (this.layoutDoc[WidthSym]() !== Math.round(this.layoutDoc[WidthSym]()) || this.layoutDoc[HeightSym]() !== Math.round(this.layoutDoc[HeightSym]()))) {
+        if (isInkMask && (this.layoutDoc[Width]() !== Math.round(this.layoutDoc[Width]()) || this.layoutDoc[Height]() !== Math.round(this.layoutDoc[Height]()))) {
             setTimeout(() => {
-                this.layoutDoc._width = Math.round(NumCast(this.layoutDoc[WidthSym]()));
-                this.layoutDoc._height = Math.round(NumCast(this.layoutDoc[HeightSym]()));
+                this.layoutDoc._width = Math.round(NumCast(this.layoutDoc[Width]()));
+                this.layoutDoc._height = Math.round(NumCast(this.layoutDoc[Height]()));
             });
         }
 
@@ -464,7 +465,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                         style={{
                             color: StrCast(this.layoutDoc.textColor, 'black'),
                             pointerEvents: this.props.isDocumentActive?.() ? 'all' : undefined,
-                            width: this.layoutDoc[WidthSym](),
+                            width: this.layoutDoc[Width](),
                             transform: `scale(${this.props.NativeDimScaling?.() || 1})`,
                             transformOrigin: 'top left',
                             //top: (this.props.PanelHeight() - (lineHeightGuess * fsize + 20) * (this.props.NativeDimScaling?.() || 1)) / 2,
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 50f451c0a..8e112dddb 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -10,7 +10,7 @@ import 'normalize.css';
 import * as React from 'react';
 import { Doc, DocListCast, Opt } from '../../fields/Doc';
 import { ScriptField } from '../../fields/ScriptField';
-import { StrCast } from '../../fields/Types';
+import { DocCast, StrCast } from '../../fields/Types';
 import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents, Utils } from '../../Utils';
 import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
 import { DocServer } from '../DocServer';
@@ -854,7 +854,7 @@ export class MainView extends React.Component {
         this._leftMenuFlyoutWidth = this._leftMenuFlyoutWidth || 250;
         //setTimeout(action(() => (this._leftMenuFlyoutWidth += 0.5)));
 
-        this._sidebarContent.proto = button.target as any;
+        this._sidebarContent.proto = DocCast(button.target);
         this.LastButton = button;
     });
 
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 00f9aa879..59865cba3 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -3,7 +3,8 @@ import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
 import * as React from 'react';
 import ReactLoading from 'react-loading';
-import { Doc, DocListCast, HeightSym, WidthSym } from '../../fields/Doc';
+import { Doc, DocListCast } from '../../fields/Doc';
+import { Height, Width } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
 import { NumCast } from '../../fields/Types';
 import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, Utils } from '../../Utils';
@@ -177,7 +178,7 @@ export class OverlayView extends React.Component {
                     offsety = 0;
                 const dref = React.createRef<HTMLDivElement>();
                 const onPointerMove = action((e: PointerEvent, down: number[]) => {
-                 if (e.cancelBubble) return false;  // if the overlay doc processed the move event (e.g., to pan its contents), then the event should be marked as canceled since propagation can't be stopped
+                    if (e.cancelBubble) return false; // if the overlay doc processed the move event (e.g., to pan its contents), then the event should be marked as canceled since propagation can't be stopped
                     if (e.buttons === 1) {
                         d.overlayX = e.clientX + offsetx;
                         d.overlayY = e.clientY + offsety;
@@ -214,8 +215,8 @@ export class OverlayView extends React.Component {
                             bringToFront={emptyFunction}
                             addDocument={undefined}
                             removeDocument={this.removeOverlayDoc}
-                            PanelWidth={d[WidthSym]}
-                            PanelHeight={d[HeightSym]}
+                            PanelWidth={d[Width]}
+                            PanelHeight={d[Height]}
                             ScreenToLocalTransform={this.docScreenToLocalXf(d)}
                             renderDepth={1}
                             hideDecorations={true}
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 5c3ed39a4..1111886d8 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -7,7 +7,8 @@ import { intersection } from 'lodash';
 import { action, computed, Lambda, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import { ColorState, SketchPicker } from 'react-color';
-import { AclAdmin, AclSym, DataSym, Doc, Field, FieldResult, HeightSym, HierarchyMapping, NumListCast, Opt, StrListCast, WidthSym } from '../../fields/Doc';
+import { Doc, Field, FieldResult, HierarchyMapping, NumListCast, Opt, StrListCast } from '../../fields/Doc';
+import { AclAdmin, DocAcl, DocData, Height, Width } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
 import { InkField } from '../../fields/InkField';
 import { List } from '../../fields/List';
@@ -69,7 +70,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
         return this.selectedDoc?.type === DocumentType.LINK;
     }
     @computed get dataDoc() {
-        return this.selectedDoc?.[DataSym];
+        return this.selectedDoc?.[DocData];
     }
 
     @observable layoutFields: boolean = false;
@@ -116,16 +117,16 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
         return this.selectedDoc?.type === DocumentType.INK;
     }
 
-    rtfWidth = () => (!this.selectedDoc ? 0 : Math.min(this.selectedDoc?.[WidthSym](), this.props.width - 20));
-    rtfHeight = () => (!this.selectedDoc ? 0 : this.rtfWidth() <= this.selectedDoc?.[WidthSym]() ? Math.min(this.selectedDoc?.[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT);
+    rtfWidth = () => (!this.selectedDoc ? 0 : Math.min(this.selectedDoc?.[Width](), this.props.width - 20));
+    rtfHeight = () => (!this.selectedDoc ? 0 : this.rtfWidth() <= this.selectedDoc?.[Width]() ? Math.min(this.selectedDoc?.[Height](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT);
 
     @action
     docWidth = () => {
         if (this.selectedDoc) {
             const layoutDoc = this.selectedDoc;
             const aspect = Doc.NativeAspect(layoutDoc, undefined, !layoutDoc._layout_fitWidth);
-            if (aspect) return Math.min(layoutDoc[WidthSym](), Math.min(this.MAX_EMBED_HEIGHT * aspect, this.props.width - 20));
-            return Doc.NativeWidth(layoutDoc) ? Math.min(layoutDoc[WidthSym](), this.props.width - 20) : this.props.width - 20;
+            if (aspect) return Math.min(layoutDoc[Width](), Math.min(this.MAX_EMBED_HEIGHT * aspect, this.props.width - 20));
+            return Doc.NativeWidth(layoutDoc) ? Math.min(layoutDoc[Width](), this.props.width - 20) : this.props.width - 20;
         } else {
             return 0;
         }
@@ -205,7 +206,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
 
     @undoBatch
     setKeyValue = (value: string) => {
-        const docs = SelectionManager.Views().length < 2 && this.selectedDoc ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc] : SelectionManager.Views().map(dv => (this.layoutFields ? dv.layoutDoc : dv.dataDoc));
+        const docs = SelectionManager.Views().length < 2 && this.selectedDoc ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc!] : SelectionManager.Views().map(dv => (this.layoutFields ? dv.layoutDoc : dv.dataDoc));
         docs.forEach(doc => {
             if (value.indexOf(':') !== -1) {
                 const newVal = value[0].toUpperCase() + value.substring(1, value.length);
@@ -220,7 +221,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                 const tags = StrListCast(doc.tags);
                 if (!tags.includes(value)) {
                     tags.push(value);
-                    doc[DataSym].tags = tags.length ? new List<string>(tags) : undefined;
+                    doc[DocData].tags = tags.length ? new List<string>(tags) : undefined;
                 }
                 return true;
             }
@@ -301,7 +302,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
      */
     @undoBatch
     changePermissions = (e: any, user: string) => {
-        const docs = (SelectionManager.Views().length < 2 ? [this.selectedDoc] : SelectionManager.Views().map(dv => dv.props.Document)).filter(doc => doc).map(doc => (this.layoutDocAcls ? doc : DocCast(doc)[DataSym]));
+        const docs = (SelectionManager.Views().length < 2 ? [this.selectedDoc] : SelectionManager.Views().map(dv => dv.props.Document)).filter(doc => doc).map(doc => (this.layoutDocAcls ? doc! : DocCast(doc)[DocData]));
         SharingManager.Instance.shareFromPropertiesSidebar(user, e.currentTarget.value as SharingPermissions, docs);
     };
 
@@ -388,7 +389,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     @computed get sharingTable() {
         // all selected docs
         const docs =
-            SelectionManager.Views().length < 2 ? [this.layoutDocAcls ? this.selectedDoc : this.selectedDoc?.[DataSym]] : SelectionManager.Views().map(docView => (this.layoutDocAcls ? docView.props.Document : docView.props.Document[DataSym]));
+            SelectionManager.Views().length < 2 && this.selectedDoc ? [this.layoutDocAcls ? this.selectedDoc : this.dataDoc!] : SelectionManager.Views().map(docView => (this.layoutDocAcls ? docView.props.Document : docView.props.Document[DocData]));
 
         const target = docs[0];
 
@@ -397,7 +398,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
         const showAdmin = effectiveAcls.every(acl => acl === AclAdmin);
 
         // users in common between all docs
-        const commonKeys: string[] = intersection(...docs.map(doc => doc?.[AclSym] && Object.keys(doc[AclSym]).filter(key => key !== 'acl-Me')));
+        const commonKeys: string[] = intersection(...docs.map(doc => doc?.[DocAcl] && Object.keys(doc[DocAcl]).filter(key => key !== 'acl-Me')));
 
         const tableEntries = [];
 
@@ -405,9 +406,9 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
         if (commonKeys.length) {
             for (const key of commonKeys) {
                 const name = denormalizeEmail(key.substring(4));
-                const uniform = docs.every(doc => doc?.[AclSym]?.[key] === docs[0]?.[AclSym]?.[key]);
+                const uniform = docs.every(doc => doc?.[DocAcl]?.[key] === docs[0]?.[DocAcl]?.[key]);
                 if (name !== Doc.CurrentUserEmail && name !== target.author && name !== 'Public' && name !== 'Override' /* && sidebarUsersDisplayed![name] !== false*/) {
-                    tableEntries.push(this.sharingItem(name, showAdmin, uniform ? HierarchyMapping.get(target[AclSym][key])!.name : '-multiple-'));
+                    tableEntries.push(this.sharingItem(name, showAdmin, uniform ? HierarchyMapping.get(target[DocAcl][key])!.name : '-multiple-'));
                 }
             }
         }
@@ -416,7 +417,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
         // shifts the current user, owner, public to the top of the doc.
         // tableEntries.unshift(this.sharingItem("Override", showAdmin, docs.filter(doc => doc).every(doc => doc["acl-Override"] === docs[0]["acl-Override"]) ? (AclMap.get(target[AclSym]?.["acl-Override"]) || "None") : "-multiple-"));
         if (ownerSame) tableEntries.unshift(this.sharingItem(StrCast(target.author), showAdmin, 'Owner'));
-        tableEntries.unshift(this.sharingItem('Public', showAdmin, docs.filter(doc => doc).every(doc => doc['acl-Public'] === target['acl-Public']) ? target['acl-Public'] || SharingPermissions.None : '-multiple-'));
+        tableEntries.unshift(this.sharingItem('Public', showAdmin, StrCast(docs.filter(doc => doc).every(doc => doc['acl-Public'] === target['acl-Public']) ? target['acl-Public'] || SharingPermissions.None : '-multiple-')));
         tableEntries.unshift(
             this.sharingItem(
                 'Me',
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 6dd76465e..dc430667b 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -14,7 +14,7 @@ import { ObjectField } from '../../../fields/ObjectField';
 import { RichTextField } from '../../../fields/RichTextField';
 import { listSpec } from '../../../fields/Schema';
 import { ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types';
+import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../fields/Types';
 import { GestureUtils } from '../../../pen-gestures/GestureUtils';
 import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, Utils } from '../../../Utils';
 import { Docs } from '../../documents/Documents';
@@ -347,7 +347,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
 
     @undoBatch
     viewChanged = (e: React.ChangeEvent) => {
-        const target = this.document !== Doc.MyLeftSidebarPanel ? this.document : (this.document.proto as Doc);
+        const target = this.document !== Doc.MyLeftSidebarPanel ? this.document : DocCast(this.document.proto);
         //@ts-ignore
         target._type_collection = e.target.selectedOptions[0].value;
     };
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index a65e23911..fc265e2b9 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -2,7 +2,8 @@ import React = require('react');
 import { CursorProperty } from 'csstype';
 import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
-import { DataSym, Doc, Field, HeightSym, Opt, WidthSym } from '../../../fields/Doc';
+import { Doc, Field, Opt } from '../../../fields/Doc';
+import { DocData, Height, Width } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
@@ -225,7 +226,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                 ref={r => (dref = r || undefined)}
                 Document={doc}
                 pointerEvents={this.blockPointerEventsWhenDragging}
-                DataDoc={dataDoc || (!Doc.AreProtosEqual(doc[DataSym], doc) && doc[DataSym])}
+                DataDoc={dataDoc ?? (!Doc.AreProtosEqual(doc[DocData], doc) ? doc[DocData] : undefined)}
                 renderDepth={this.props.renderDepth + 1}
                 PanelWidth={width}
                 PanelHeight={height}
@@ -284,7 +285,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
         const existingHeader = this.colHeaderData.find(sh => sh.heading === heading);
         const existingWidth = existingHeader?.width ? existingHeader.width : 0;
         const maxWidth = existingWidth > 0 ? existingWidth * this.availableWidth : this.maxColWidth;
-        const width = d.layout_fitWidth ? maxWidth : d[WidthSym]();
+        const width = d.layout_fitWidth ? maxWidth : d[Width]();
         return Math.min(maxWidth - CollectionNoteTakingViewColumn.ColumnMargin, width < maxWidth ? width : maxWidth);
     }
 
@@ -294,8 +295,8 @@ export class CollectionNoteTakingView extends CollectionSubView() {
         const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
         const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField ? undefined : this.props.DataDoc;
         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)) ? d[WidthSym]() : 0);
-        const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? d[HeightSym]() : 0);
+        const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? d[Width]() : 0);
+        const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? d[Height]() : 0);
         if (nw && nh) {
             const docWid = this.getDocWidth(d);
             return Math.min(maxHeight, (docWid * nh) / nw);
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index ea0fbbc54..bbc91aa6e 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -1,6 +1,7 @@
 import { action, computed, IReactionDisposer, reaction } from 'mobx';
 import { observer } from 'mobx-react';
-import { Doc, DocListCast, HeightSym, WidthSym } from '../../../fields/Doc';
+import { Doc, DocListCast } from '../../../fields/Doc';
+import { Height, Width } from '../../../fields/DocSymbols';
 import { NumCast, StrCast } from '../../../fields/Types';
 import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils';
 import { DocUtils } from '../../documents/Documents';
@@ -73,12 +74,12 @@ export class CollectionPileView extends CollectionSubView() {
     // toggles the pileup between starburst to compact
     toggleStarburst = action(() => {
         if (this.layoutEngine() === computeStarburstLayout.name) {
-            if (this.rootDoc[WidthSym]() !== NumCast(this.rootDoc._starburstDiameter, 500)) {
-                this.rootDoc._starburstDiameter = this.rootDoc[WidthSym]();
+            if (this.rootDoc[Width]() !== NumCast(this.rootDoc._starburstDiameter, 500)) {
+                this.rootDoc._starburstDiameter = this.rootDoc[Width]();
             }
             const defaultSize = 110;
-            this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - NumCast(this.layoutDoc._starburstPileWidth, defaultSize) / 2;
-            this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - NumCast(this.layoutDoc._starburstPileHeight, defaultSize) / 2;
+            this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[Width]() / 2 - NumCast(this.layoutDoc._starburstPileWidth, defaultSize) / 2;
+            this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[Height]() / 2 - NumCast(this.layoutDoc._starburstPileHeight, defaultSize) / 2;
             this.layoutDoc._width = NumCast(this.layoutDoc._starburstPileWidth, defaultSize);
             this.layoutDoc._height = NumCast(this.layoutDoc._starburstPileHeight, defaultSize);
             DocUtils.pileup(this.childDocs, undefined, undefined, NumCast(this.layoutDoc._width) / 2, false);
@@ -87,10 +88,10 @@ export class CollectionPileView extends CollectionSubView() {
             this.props.Document._pileLayoutEngine = computePassLayout.name;
         } else {
             const defaultSize = NumCast(this.rootDoc._starburstDiameter, 500);
-            this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - defaultSize / 2;
-            this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - defaultSize / 2;
-            this.layoutDoc._starburstPileWidth = this.layoutDoc[WidthSym]();
-            this.layoutDoc._starburstPileHeight = this.layoutDoc[HeightSym]();
+            this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[Width]() / 2 - defaultSize / 2;
+            this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[Height]() / 2 - defaultSize / 2;
+            this.layoutDoc._starburstPileWidth = this.layoutDoc[Width]();
+            this.layoutDoc._starburstPileHeight = this.layoutDoc[Height]();
             this.layoutDoc._freeform_panX = this.layoutDoc._freeform_panY = 0;
             this.layoutDoc._width = this.layoutDoc._height = defaultSize;
             this.props.Document._pileLayoutEngine = computeStarburstLayout.name;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index e2e352857..56f53afe6 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -3,7 +3,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { CursorProperty } from 'csstype';
 import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
-import { DataSym, Doc, HeightSym, Opt, WidthSym } from '../../../fields/Doc';
+import { Doc, Opt } from '../../../fields/Doc';
+import { DocData, Height, Width } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
@@ -285,7 +286,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
             const layout_fieldKey = StrCast(docView.LayoutFieldKey);
             const newDoc = Doc.MakeCopy(docView.rootDoc, true);
             const dataField = docView.rootDoc[Doc.LayoutFieldKey(newDoc)];
-            newDoc[DataSym][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined;
+            newDoc[DocData][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined;
             if (layout_fieldKey !== 'layout' && docView.rootDoc[layout_fieldKey] instanceof Doc) {
                 newDoc[layout_fieldKey] = docView.rootDoc[layout_fieldKey];
             }
@@ -320,7 +321,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
             <DocumentView
                 ref={r => (dref = r || undefined)}
                 Document={doc}
-                DataDoc={dataDoc || (!Doc.AreProtosEqual(doc[DataSym], doc) && doc[DataSym])}
+                DataDoc={dataDoc ?? (!Doc.AreProtosEqual(doc[DocData], doc) ? doc[DocData] : undefined)}
                 renderDepth={this.props.renderDepth + 1}
                 PanelWidth={width}
                 PanelHeight={height}
@@ -377,7 +378,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
         const maxWidth = this.columnWidth / this.numGroupColumns;
         if (!this.layoutDoc._columnsFill && !(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d))) {
-            return Math.min(d[WidthSym](), maxWidth);
+            return Math.min(d[Width](), maxWidth);
         }
         return maxWidth;
     }
@@ -386,8 +387,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
         const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField ? undefined : this.props.DataDoc;
         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)) ? d[WidthSym]() : 0);
-        const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? d[HeightSym]() : 0);
+        const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? d[Width]() : 0);
+        const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? d[Height]() : 0);
         if (nw && nh) {
             const colWid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1);
             const docWid = this.layoutDoc._columnsFill ? colWid : Math.min(this.getDocWidth(d), colWid);
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 095e34c39..969658238 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,6 +1,7 @@
 import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
-import { DataSym, Doc, DocListCast, HeightSym, Opt, StrListCast, WidthSym } from '../../../fields/Doc';
+import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc';
+import { DocData, Height, Width } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { listSpec } from '../../../fields/Schema';
@@ -154,15 +155,15 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     @action
     remove = (doc: Doc | Doc[]): boolean => {
         const docs = doc instanceof Doc ? [doc] : doc;
-        const targetDataDoc = this.doc[DataSym];
+        const targetDataDoc = this.doc[DocData];
         const value = DocListCast(targetDataDoc[this.props.fieldKey]);
         const result = value.filter(v => !docs.includes(v));
         if ((doc instanceof Doc ? [doc] : doc).some(doc => SelectionManager.Views().some(dv => Doc.AreProtosEqual(dv.rootDoc, doc)))) SelectionManager.DeselectAll();
-        if (result.length !== value.length) {
-            const ind = targetDataDoc[this.props.fieldKey].indexOf(doc);
-            const prev = ind && targetDataDoc[this.props.fieldKey][ind - 1];
+        if (result.length !== value.length && doc instanceof Doc) {
+            const ind = DocListCast(targetDataDoc[this.props.fieldKey]).indexOf(doc);
+            const prev = ind && DocListCast(targetDataDoc[this.props.fieldKey])[ind - 1];
             this.props.removeDocument?.(doc);
-            if (ind > 0) {
+            if (ind > 0 && prev) {
                 FormattedTextBox.SelectOnLoad = prev[Id];
                 DocumentManager.Instance.getDocumentView(prev, this.props.DocumentView?.())?.select(false);
             }
@@ -175,7 +176,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     addDoc = (docs: Doc | Doc[], relativeTo: Opt<Doc>, before?: boolean): boolean => {
         const doAddDoc = (doc: Doc | Doc[]) =>
             (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => {
-                const res = flg && Doc.AddDocToList(this.doc[DataSym], this.props.fieldKey, doc, relativeTo, before);
+                const res = flg && Doc.AddDocToList(this.doc[DocData], this.props.fieldKey, doc, relativeTo, before);
                 res && (doc.embedContainer = this.props.Document);
                 return res;
             }, true);
@@ -368,8 +369,8 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     marginX = () => NumCast(this.doc._xMargin);
     marginTop = () => NumCast(this.doc._yMargin);
     marginBot = () => NumCast(this.doc._yMargin);
-    documentTitleWidth = () => Math.min(this.layoutDoc?.[WidthSym](), this.panelWidth());
-    documentTitleHeight = () => (this.layoutDoc?.[HeightSym]() || 0) - NumCast(this.layoutDoc.layout_autoHeightMargins);
+    documentTitleWidth = () => Math.min(this.layoutDoc?.[Width](), this.panelWidth());
+    documentTitleHeight = () => (this.layoutDoc?.[Height]() || 0) - NumCast(this.layoutDoc.layout_autoHeightMargins);
     truncateTitleWidth = () => this.treeViewtruncateTitleWidth;
     onChildClick = () => this.props.onChildClick?.() || ScriptCast(this.doc.onChildClick);
     panelWidth = () => Math.max(0, this.props.PanelWidth() - 2 * this.marginX() * (this.props.NativeDimScaling?.() || 1));
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index b20d05433..710e33b1c 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -5,11 +5,11 @@ import { clamp } from 'lodash';
 import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as ReactDOM from 'react-dom/client';
-import { DataSym, Doc, HeightSym, Opt, WidthSym } from '../../../fields/Doc';
+import { Doc, Opt } from '../../../fields/Doc';
+import { DocData, Height, Width } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { FieldId } from '../../../fields/RefField';
-import { listSpec } from '../../../fields/Schema';
 import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../fields/Types';
 import { emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick, Utils } from '../../../Utils';
 import { DocServer } from '../../DocServer';
@@ -292,7 +292,8 @@ export class TabDocView extends React.Component<TabDocViewProps> {
             PresBox.Instance?.clearSelectedArray();
             pinDoc && PresBox.Instance?.addToSelectedArray(pinDoc); //Update selected array
         });
-        if ( // open the presentation trail if it's not already opened
+        if (
+            // open the presentation trail if it's not already opened
             !Array.from(CollectionDockingView.Instance?.tabMap ?? [])
                 .map(d => d.DashDoc)
                 .includes(curPres)
@@ -423,7 +424,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                     LayoutTemplateString={this.props.keyValue ? KeyValueBox.LayoutString() : undefined}
                     hideTitle={this.props.keyValue}
                     Document={this._document}
-                    DataDoc={!Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined}
+                    DataDoc={!Doc.AreProtosEqual(this._document[DocData], this._document) ? this._document[DocData] : undefined}
                     onBrowseClick={MainView.Instance.exploreMode}
                     waitForDoubleClickToClick={MainView.Instance.waitForDoubleClick}
                     isContentActive={returnTrue}
@@ -533,7 +534,7 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
                                 return 'gray';
                         }
                     })(doc.type as DocumentType);
-                    return !background ? undefined : <div style={{ width: doc[WidthSym](), height: doc[HeightSym](), position: 'absolute', display: 'block', background }} />;
+                    return !background ? undefined : <div style={{ width: doc[Width](), height: doc[Height](), position: 'absolute', display: 'block', background }} />;
             }
         }
     };
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index f5877fa1a..ca9471f6f 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -2,7 +2,8 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
-import { DataSym, Doc, DocListCast, Field, HeightSym, Opt, StrListCast, WidthSym } from '../../../fields/Doc';
+import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc';
+import { DocData, Height, Width } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { RichTextField } from '../../../fields/RichTextField';
@@ -146,7 +147,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         return NumCast(this.props.treeViewParent.maxEmbedHeight, 200);
     }
     @computed get dataDoc() {
-        return this.props.document.treeViewChildrenOnRoot ? this.doc : this.doc[DataSym];
+        return this.props.document.treeViewChildrenOnRoot ? this.doc : this.doc[DocData];
     }
     @computed get layoutDoc() {
         return Doc.Layout(this.doc);
@@ -191,10 +192,10 @@ export class TreeView extends React.Component<TreeViewProps> {
     };
     @undoBatch @action remove = (doc: Doc | Doc[], key: string) => {
         this.props.treeView.props.select(false);
-        const ind = this.dataDoc[key].indexOf(doc);
+        const ind = DocListCast(this.dataDoc[key]).indexOf(doc instanceof Doc ? doc : doc.lastElement());
 
         const res = (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true);
-        res && ind > 0 && DocumentManager.Instance.getDocumentView(this.dataDoc[key][ind - 1], this.props.treeView.props.DocumentView?.())?.select(false);
+        res && ind > 0 && DocumentManager.Instance.getDocumentView(DocListCast(this.dataDoc[key])[ind - 1], this.props.treeView.props.DocumentView?.())?.select(false);
         return res;
     };
 
@@ -410,7 +411,7 @@ export class TreeView extends React.Component<TreeViewProps> {
     embeddedPanelHeight = () => {
         const layoutDoc = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc;
         return Math.min(
-            layoutDoc[HeightSym](),
+            layoutDoc[Height](),
             this.MAX_EMBED_HEIGHT,
             (() => {
                 const aspect = Doc.NativeAspect(layoutDoc);
@@ -419,7 +420,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                     ? !Doc.NativeHeight(layoutDoc)
                         ? NumCast(layoutDoc._height)
                         : Math.min((this.embeddedPanelWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / (Doc.NativeWidth(layoutDoc) || NumCast(this.props.treeViewParent._height)))
-                    : (this.embeddedPanelWidth() * layoutDoc[HeightSym]()) / layoutDoc[WidthSym]();
+                    : (this.embeddedPanelWidth() * layoutDoc[Height]()) / layoutDoc[Width]();
             })()
         );
     };
@@ -1190,7 +1191,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                 const childLayout = Doc.Layout(pair.layout);
                 const rowHeight = () => {
                     const aspect = Doc.NativeAspect(childLayout);
-                    return aspect ? Math.min(childLayout[WidthSym](), rowWidth()) / aspect : childLayout[HeightSym]();
+                    return aspect ? Math.min(childLayout[Width](), rowWidth()) / aspect : childLayout[Height]();
                 };
                 return (
                     <TreeView
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index fee4705e6..1e76d373c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -1,4 +1,5 @@
-import { Doc, Field, FieldResult, HeightSym, WidthSym } from '../../../../fields/Doc';
+import { Doc, Field, FieldResult } from '../../../../fields/Doc';
+import { Height, Width } from '../../../../fields/DocSymbols';
 import { Id, ToString } from '../../../../fields/FieldSymbols';
 import { ObjectField } from '../../../../fields/ObjectField';
 import { RefField } from '../../../../fields/RefField';
@@ -90,8 +91,8 @@ export function computePassLayout(poolData: Map<string, PoolData>, pivotDoc: Doc
         docMap.set(layout[Id], {
             x: NumCast(layout.x),
             y: NumCast(layout.y),
-            width: layout[WidthSym](),
-            height: layout[HeightSym](),
+            width: layout[Width](),
+            height: layout[Height](),
             pair: { layout, data },
             transition: 'all .3s',
             replica: '',
@@ -105,8 +106,8 @@ export function computeStarburstLayout(poolData: Map<string, PoolData>, pivotDoc
     const burstDiam = [NumCast(pivotDoc._width), NumCast(pivotDoc._height)];
     const burstScale = NumCast(pivotDoc._starburstDocScale, 1);
     childPairs.forEach(({ layout, data }, i) => {
-        const aspect = layout[HeightSym]() / layout[WidthSym]();
-        const docSize = Math.min(Math.min(400, layout[WidthSym]()), Math.min(400, layout[WidthSym]()) / aspect) * burstScale;
+        const aspect = layout[Height]() / layout[Width]();
+        const docSize = Math.min(Math.min(400, layout[Width]()), Math.min(400, layout[Width]()) / aspect) * burstScale;
         const deg = (i / childPairs.length) * Math.PI * 2;
         docMap.set(layout[Id], {
             x: Math.min(burstDiam[0] / 2 - docSize, Math.max(-burstDiam[0] / 2, (Math.cos(deg) * burstDiam[0]) / 2 - docSize / 2)),
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 8b9698293..811a77fa8 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -4,7 +4,8 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction,
 import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
 import { DateField } from '../../../../fields/DateField';
-import { DataSym, Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../../fields/Doc';
+import { Doc, DocListCast, Opt } from '../../../../fields/Doc';
+import { DocData, Height, Width } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkData, InkField, InkTool, PointData, Segment } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
@@ -707,7 +708,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             case GestureUtils.Gestures.Rectangle:
                 if (this._inkToTextStartX && this._inkToTextStartY) {
                     const end = this.getTransform().transformPoint(Math.max(...ge.points.map(p => p.X)), Math.max(...ge.points.map(p => p.Y)));
-                    const setDocs = this.getActiveDocuments().filter(s => s.proto?.type === 'rtf' && s.color);
+                    const setDocs = this.getActiveDocuments().filter(s => DocCast(s.proto)?.type === DocumentType.RTF && s.color);
                     const sets = setDocs.map(sd => {
                         return Cast(sd.text, RichTextField)?.Text as string;
                     });
@@ -719,9 +720,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     const inks = this.getActiveDocuments().filter(doc => {
                         if (doc.type === 'ink') {
                             const l = NumCast(doc.x);
-                            const r = l + doc[WidthSym]();
+                            const r = l + doc[Width]();
                             const t = NumCast(doc.y);
-                            const b = t + doc[HeightSym]();
+                            const b = t + doc[Height]();
                             const pass = !(this._inkToTextStartX! > r || end[0] < l || this._inkToTextStartY! > b || end[1] < t);
                             return pass;
                         }
@@ -1201,7 +1202,7 @@ 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) + layoutdoc[WidthSym](), NumCast(doc.y) + layoutdoc[HeightSym]());
+        const pt2 = xf.transformPoint(NumCast(doc.x) + layoutdoc[Width](), NumCast(doc.y) + layoutdoc[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) {
@@ -1251,7 +1252,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             const layout_fieldKey = StrCast(docView.LayoutFieldKey);
             const newDoc = Doc.MakeCopy(docView.rootDoc, true);
             const dataField = docView.rootDoc[Doc.LayoutFieldKey(newDoc)];
-            newDoc[DataSym][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined;
+            newDoc[DocData][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined;
             if (below) newDoc.y = NumCast(docView.rootDoc.y) + NumCast(docView.rootDoc._height) + 10;
             else newDoc.x = NumCast(docView.rootDoc.x) + NumCast(docView.rootDoc._width) + 10;
             if (layout_fieldKey !== 'layout' && docView.rootDoc[layout_fieldKey] instanceof Doc) {
@@ -1291,8 +1292,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 onDoubleClick={this.onChildDoubleClickHandler}
                 onBrowseClick={this.onBrowseClickHandler}
                 ScreenToLocalTransform={childLayout.z ? this.getContainerTransform : this.getTransform}
-                PanelWidth={childLayout[WidthSym]}
-                PanelHeight={childLayout[HeightSym]}
+                PanelWidth={childLayout[Width]}
+                PanelHeight={childLayout[Height]}
                 docFilters={this.childDocFilters}
                 docRangeFilters={this.childDocRangeFilters}
                 searchFilterDocs={this.searchFilterDocs}
@@ -1558,14 +1559,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 this._disposers.groupBounds = reaction(
                     () => {
                         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: cd[WidthSym](), height: cd[HeightSym]() }));
+                            const clist = this.childDocs.map(cd => ({ x: NumCast(cd.x), y: NumCast(cd.y), width: cd[Width](), height: cd[Height]() }));
                             return aggregateBounds(clist, NumCast(this.layoutDoc._xPadding), NumCast(this.layoutDoc._yPadding));
                         }
                         return undefined;
                     },
                     cbounds => {
                         if (cbounds) {
-                            const c = [NumCast(this.layoutDoc.x) + this.layoutDoc[WidthSym]() / 2, NumCast(this.layoutDoc.y) + this.layoutDoc[HeightSym]() / 2];
+                            const c = [NumCast(this.layoutDoc.x) + this.layoutDoc[Width]() / 2, NumCast(this.layoutDoc.y) + this.layoutDoc[Height]() / 2];
                             const p = [NumCast(this.layoutDoc[this.panXFieldKey]), NumCast(this.layoutDoc[this.panYFieldKey])];
                             const pbounds = {
                                 x: cbounds.x - p[0] + c[0],
@@ -1631,8 +1632,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         CollectionFreeFormView.UpdateIcon(
             this.layoutDoc[Id] + '-icon' + new Date().getTime(),
             this.props.docViewPath().lastElement().ContentDiv!,
-            this.layoutDoc[WidthSym](),
-            this.layoutDoc[HeightSym](),
+            this.layoutDoc[Width](),
+            this.layoutDoc[Height](),
             this.props.PanelWidth(),
             this.props.PanelHeight(),
             0,
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 641088675..d50c8d2c4 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,6 +1,7 @@
 import { action, computed, observable } from 'mobx';
 import { observer } from 'mobx-react';
-import { AclAdmin, AclAugment, AclEdit, DataSym, Doc, Opt } from '../../../../fields/Doc';
+import { Doc, Opt } from '../../../../fields/Doc';
+import { AclAdmin, AclAugment, AclEdit, DocData } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkData, InkField, InkTool } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
@@ -325,7 +326,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         } else {
             this._downX = x;
             this._downY = y;
-            const effectiveAcl = GetEffectiveAcl(this.props.Document[DataSym]);
+            const effectiveAcl = GetEffectiveAcl(this.props.Document[DocData]);
             if ([AclAdmin, AclEdit, AclAugment].includes(effectiveAcl)) {
                 PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument, this.props.nudge, this.props.slowLoadDocuments);
             }
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index 3561844da..ca6b8705d 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -3,9 +3,10 @@ import { Tooltip } from '@material-ui/core';
 import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Doc, HeightSym, Opt, WidthSym } from '../../../../fields/Doc';
+import { Doc, Opt } from '../../../../fields/Doc';
+import { Height, Width } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
-import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
+import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { emptyFunction, returnEmptyDoclist, returnTrue, StopEvent, Utils } from '../../../../Utils';
 import { CollectionViewType } from '../../../documents/DocumentTypes';
 import { DocumentManager } from '../../../util/DocumentManager';
@@ -40,12 +41,12 @@ export class CollectionLinearView extends CollectionSubView() {
         this._dropDisposer?.();
         this._widthDisposer?.();
         this._selectedDisposer?.();
-        this.childLayoutPairs.map((pair, ind) => ScriptCast(pair.layout.proto?.onPointerUp)?.script.run({ this: pair.layout.proto }, console.log));
+        this.childLayoutPairs.map((pair, ind) => ScriptCast(DocCast(pair.layout.proto)?.onPointerUp)?.script.run({ this: pair.layout.proto }, console.log));
     }
 
     componentDidMount() {
         this._widthDisposer = reaction(
-            () => 5 + NumCast(this.rootDoc.linearBtnWidth, this.dimension()) + (this.layoutDoc.linearViewIsExpanded ? this.childDocs.filter(doc => !doc.hidden).reduce((tot, doc) => (doc[WidthSym]() || this.dimension()) + tot + 4, 0) : 0),
+            () => 5 + NumCast(this.rootDoc.linearBtnWidth, this.dimension()) + (this.layoutDoc.linearViewIsExpanded ? this.childDocs.filter(doc => !doc.hidden).reduce((tot, doc) => (doc[Width]() || this.dimension()) + tot + 4, 0) : 0),
             width => this.childDocs.length && (this.layoutDoc._width = width),
             { fireImmediately: true }
         );
@@ -61,11 +62,11 @@ export class CollectionLinearView extends CollectionSubView() {
                         if (isSelected) {
                             selected = pair;
                         } else {
-                            ScriptCast(pair.layout.proto?.onPointerUp)?.script.run({ this: pair.layout.proto }, console.log);
+                            ScriptCast(DocCast(pair.layout.proto)?.onPointerUp)?.script.run({ this: pair.layout.proto }, console.log);
                         }
                     });
                     if (selected && selected.layout) {
-                        ScriptCast(selected.layout.proto?.onPointerDown)?.script.run({ this: selected.layout.proto }, console.log);
+                        ScriptCast(DocCast(selected.layout.proto)?.onPointerDown)?.script.run({ this: selected.layout.proto }, console.log);
                     }
                 }),
             { fireImmediately: true }
@@ -204,8 +205,8 @@ export class CollectionLinearView extends CollectionSubView() {
                     rootSelected={this.props.isSelected}
                     removeDocument={this.props.removeDocument}
                     ScreenToLocalTransform={docXf}
-                    PanelWidth={doc[WidthSym]}
-                    PanelHeight={nested || doc._height ? doc[HeightSym] : this.dimension}
+                    PanelWidth={doc[Width]}
+                    PanelHeight={nested || doc._height ? doc[Height] : this.dimension}
                     renderDepth={this.props.renderDepth + 1}
                     dontRegisterView={BoolCast(this.rootDoc.childDontRegisterViews)}
                     focus={emptyFunction}
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 01bfbcddb..876693474 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -6,7 +6,7 @@ import DatePicker from 'react-datepicker';
 import { DateField } from '../../../../fields/DateField';
 import { Doc, DocListCast, Field } from '../../../../fields/Doc';
 import { RichTextField } from '../../../../fields/RichTextField';
-import { BoolCast, Cast, DateCast, FieldValue } from '../../../../fields/Types';
+import { BoolCast, Cast, DateCast, DocCast, FieldValue } from '../../../../fields/Types';
 import { ImageField } from '../../../../fields/URLField';
 import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero, Utils } from '../../../../Utils';
 import { FInfo } from '../../../documents/Documents';
@@ -58,7 +58,7 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
                 break;
             }
             protoCount++;
-            doc = doc.proto;
+            doc = DocCast(doc.proto);
         }
         const parenCount = Math.max(0, protoCount - 1);
         const color = protoCount === 0 || (fieldKey.startsWith('_') && Document[fieldKey] === undefined) ? 'black' : 'blue';
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index 330cc3971..aae759702 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -2,7 +2,8 @@ import React = require('react');
 import { action } from 'mobx';
 import { observer } from 'mobx-react';
 import { ColorState, SketchPicker } from 'react-color';
-import { Doc, HeightSym, WidthSym } from '../../../fields/Doc';
+import { Doc } from '../../../fields/Doc';
+import { Height, Width } from '../../../fields/DocSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { StrCast } from '../../../fields/Types';
 import { DocumentType } from '../../documents/DocumentTypes';
@@ -48,7 +49,7 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 
     render() {
-        const scaling = Math.min(this.layoutDoc.layout_fitWidth ? 10000 : this.props.PanelHeight() / this.rootDoc[HeightSym](), this.props.PanelWidth() / this.rootDoc[WidthSym]());
+        const scaling = Math.min(this.layoutDoc.layout_fitWidth ? 10000 : this.props.PanelHeight() / this.rootDoc[Height](), this.props.PanelWidth() / this.rootDoc[Width]());
         return (
             <div
                 className={`colorBox-container${this.props.isContentActive() ? '-interactive' : ''}`}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index cc105326f..1c7ef5217 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -3,7 +3,8 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction
 import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
 import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal';
-import { AclPrivate, AnimationSym, DataSym, Doc, DocListCast, Field, Opt, StrListCast, WidthSym } from '../../../fields/Doc';
+import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc';
+import { AclPrivate, Animation, DocData, Width } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
@@ -794,7 +795,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         const helpItems: ContextMenuProps[] = help && 'subitems' in help ? help.subitems : [];
         !Doc.noviceMode && helpItems.push({ description: 'Text Shortcuts Ctrl+/', event: () => this.props.addDocTab(Docs.Create.PdfDocument('/assets/cheat-sheet.pdf', { _width: 300, _height: 300 }), OpenWhere.addRight), icon: 'keyboard' });
         !Doc.noviceMode && helpItems.push({ description: 'Print Document in Console', event: () => console.log(this.props.Document), icon: 'hand-point-right' });
-        !Doc.noviceMode && helpItems.push({ description: 'Print DataDoc in Console', event: () => console.log(this.props.Document[DataSym]), icon: 'hand-point-right' });
+        !Doc.noviceMode && helpItems.push({ description: 'Print DataDoc in Console', event: () => console.log(this.props.Document[DocData]), icon: 'hand-point-right' });
 
         let documentationDescription: string | undefined = undefined;
         let documentationLink: string | undefined = undefined;
@@ -1146,7 +1147,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
     renderDoc = (style: object) => {
         TraceMobx();
-        return !DocCast(this.Document) || GetEffectiveAcl(this.Document[DataSym]) === AclPrivate
+        return !DocCast(this.Document) || GetEffectiveAcl(this.Document[DocData]) === AclPrivate
             ? null
             : this.docContents ?? (
                   <div
@@ -1231,7 +1232,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     pointerEvents: this.pointerEvents === 'visiblePainted' ? 'none' : this.pointerEvents,
                 }}>
                 <>
-                    {DocumentViewInternal.AnimationEffect(renderDoc, this.rootDoc[AnimationSym], this.rootDoc)}
+                    {DocumentViewInternal.AnimationEffect(renderDoc, this.rootDoc[Animation], this.rootDoc)}
                     {borderPath?.jsx}
                 </>
             </div>
@@ -1265,8 +1266,8 @@ export class DocumentView extends React.Component<DocumentViewProps> {
 
     public setAnimEffect = (presEffect: Doc, timeInMs: number, afterTrans?: () => void) => {
         this.AnimEffectTimer && clearTimeout(this.AnimEffectTimer);
-        this.rootDoc[AnimationSym] = presEffect;
-        this.AnimEffectTimer = setTimeout(() => (this.rootDoc[AnimationSym] = undefined), timeInMs);
+        this.rootDoc[Animation] = presEffect;
+        this.AnimEffectTimer = setTimeout(() => (this.rootDoc[Animation] = undefined), timeInMs);
     };
     public setViewTransition = (transProp: string, timeInMs: number, afterTrans?: () => void, dataTrans = false) => {
         this.rootDoc._viewTransition = `${transProp} ${timeInMs}ms`;
@@ -1579,7 +1580,7 @@ ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuff
 ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc) {
     const linkSource = Cast(linkCollection.linkSource, Doc, null);
     const collectedLinks = DocListCast(Doc.GetProto(linkCollection).data);
-    let wid = linkSource[WidthSym]();
+    let wid = linkSource[Width]();
     const links = LinkManager.Links(linkSource);
     links.forEach(link => {
         const other = LinkManager.getOppositeAnchor(link, linkSource);
@@ -1589,7 +1590,7 @@ ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc) {
             embedding.x = wid;
             embedding.y = 0;
             embedding._lockedPosition = false;
-            wid += otherdoc[WidthSym]();
+            wid += otherdoc[Width]();
             Doc.AddDocToList(Doc.GetProto(linkCollection), 'data', embedding);
         }
     });
diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx
index f17ab06e7..8c6eb4532 100644
--- a/src/client/views/nodes/EquationBox.tsx
+++ b/src/client/views/nodes/EquationBox.tsx
@@ -2,7 +2,7 @@ import EquationEditor from 'equation-editor-react';
 import { action, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { WidthSym } from '../../../fields/Doc';
+import { Width } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { NumCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
@@ -67,7 +67,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
         }
         if (e.key === 'Tab') {
             const graph = Docs.Create.FunctionPlotDocument([this.rootDoc], {
-                x: NumCast(this.layoutDoc.x) + this.layoutDoc[WidthSym](),
+                x: NumCast(this.layoutDoc.x) + this.layoutDoc[Width](),
                 y: NumCast(this.layoutDoc.y),
                 _width: 400,
                 _height: 300,
@@ -104,7 +104,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     fontSize: StrCast(this.rootDoc._fontSize),
                 }}
                 onKeyDown={e => e.stopPropagation()}>
-                <EquationEditor ref={this._ref} value={this.dataDoc.text || 'x'} spaceBehavesLikeTab={true} onChange={this.onChange} autoCommands="pi theta sqrt sum prod alpha beta gamma rho" autoOperatorNames="sin cos tan" />
+                <EquationEditor ref={this._ref} value={StrCast(this.dataDoc.text, 'x')} spaceBehavesLikeTab={true} onChange={this.onChange} autoCommands="pi theta sqrt sum prod alpha beta gamma rho" autoOperatorNames="sin cos tan" />
             </div>
         );
     }
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 068d39391..5b302e7ce 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -3,7 +3,8 @@ import { Tooltip } from '@material-ui/core';
 import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { extname } from 'path';
-import { DataSym, Doc, DocListCast, Opt, WidthSym } from '../../../fields/Doc';
+import { Doc, DocListCast, Opt } from '../../../fields/Doc';
+import { DocData, Width } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
@@ -106,7 +107,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         );
         const layoutDoc = this.layoutDoc;
         this._disposers.path = reaction(
-            () => ({ nativeSize: this.nativeSize, width: this.layoutDoc[WidthSym]() }),
+            () => ({ nativeSize: this.nativeSize, width: this.layoutDoc[Width]() }),
             ({ nativeSize, width }) => {
                 if (layoutDoc === this.layoutDoc || !this.layoutDoc._height) {
                     this.layoutDoc._height = (width * nativeSize.nativeHeight) / nativeSize.nativeWidth;
@@ -150,7 +151,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             } else if (de.altKey || !this.dataDoc[this.fieldKey]) {
                 const layoutDoc = de.complete.docDragData?.draggedDocuments[0];
                 const targetField = Doc.LayoutFieldKey(layoutDoc);
-                const targetDoc = layoutDoc[DataSym];
+                const targetDoc = layoutDoc[DocData];
                 if (targetDoc[targetField] instanceof ImageField) {
                     this.dataDoc[this.fieldKey] = ObjectField.MakeCopy(targetDoc[targetField] as ImageField);
                     Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(targetDoc), this.fieldKey);
@@ -230,7 +231,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         croppingProto.freeform_panYMax = anchh / viewScale;
         if (addCrop) {
             DocUtils.MakeLink(region, cropping, { link_relationship: 'cropped image' });
-            cropping.x = NumCast(this.rootDoc.x) + this.rootDoc[WidthSym]();
+            cropping.x = NumCast(this.rootDoc.x) + this.rootDoc[Width]();
             cropping.y = NumCast(this.rootDoc.y);
             this.props.addDocTab(cropping, OpenWhere.inParent);
         }
@@ -302,7 +303,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     }
 
     considerGooglePhotosLink = () => {
-        const remoteUrl = this.dataDoc.googlePhotosUrl;
+        const remoteUrl = StrCast(this.dataDoc.googlePhotosUrl); // bcz: StrCast or URLCast???
         return !remoteUrl ? null : <img draggable={false} style={{ transformOrigin: 'bottom right' }} id={'google-photos'} src={'/assets/google_photos.png'} onClick={() => window.open(remoteUrl)} />;
     };
 
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 88a82e8e6..5b6b0b5a7 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -84,7 +84,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     public static ApplyKVPScript(doc: Doc, key: string, kvpScript: KVPScript, forceOnDelegate?: boolean): boolean {
         const { script, type, onDelegate } = kvpScript;
         //const target = onDelegate ? Doc.Layout(doc.layout) : Doc.GetProto(doc); // bcz: TODO need to be able to set fields on layout templates
-        const target = forceOnDelegate || onDelegate || key.startsWith('_') ? doc : doc.proto || doc;
+        const target = forceOnDelegate || onDelegate || key.startsWith('_') ? doc : DocCast(doc.proto, doc);
         let field: Field;
         if (type === 'computed') {
             field = new ComputedField(script);
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 91df928c4..4e348c67c 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -13,6 +13,7 @@ import { KeyValueBox } from './KeyValueBox';
 import './KeyValueBox.scss';
 import './KeyValuePair.scss';
 import React = require('react');
+import { DocCast } from '../../../fields/Types';
 
 // Represents one row in a key value plane
 
@@ -87,7 +88,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
                 break;
             }
             protoCount++;
-            doc = doc.proto;
+            doc = DocCast(doc.proto);
         }
         const parenCount = Math.max(0, protoCount - 1);
         const keyStyle = protoCount === 0 ? 'black' : 'blue';
@@ -104,12 +105,12 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
                             onClick={undoBatch(() => {
                                 if (Object.keys(props.Document).indexOf(props.fieldKey) !== -1) {
                                     delete props.Document[props.fieldKey];
-                                } else delete props.Document.proto![props.fieldKey];
+                                } else delete DocCast(props.Document.proto)?.[props.fieldKey];
                             })}>
                             X
                         </button>
                         <input className="keyValuePair-td-key-check" type="checkbox" style={hover} onChange={this.handleCheck} ref={this.checkbox} />
-                        <div className='keyValuePair-keyField' style={{ marginLeft: 35 * (props.fieldKey.match(/_/g)?.length ||0), color: keyStyle }}>
+                        <div className="keyValuePair-keyField" style={{ marginLeft: 35 * (props.fieldKey.match(/_/g)?.length || 0), color: keyStyle }}>
                             {'('.repeat(parenCount)}
                             {props.fieldKey}
                             {')'.repeat(parenCount)}
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 450176a49..079e1b56d 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -3,11 +3,12 @@ import { Tooltip } from '@material-ui/core';
 import { action, computed, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import wiki from 'wikijs';
-import { Doc, DocCastAsync, HeightSym, Opt, WidthSym } from '../../../fields/Doc';
+import { Doc, DocCastAsync, Opt } from '../../../fields/Doc';
+import { Height, Width } from '../../../fields/DocSymbols';
 import { Cast, DocCast, NumCast, PromiseValue, StrCast } from '../../../fields/Types';
 import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnNone, setupMoveUpEvents } from '../../../Utils';
 import { DocServer } from '../../DocServer';
-import { Docs, DocUtils } from '../../documents/Documents';
+import { Docs } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { DragManager } from '../../util/DragManager';
 import { LinkFollower } from '../../util/LinkFollower';
@@ -183,17 +184,17 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
 
     width = () => {
         if (!this._targetDoc) return 225;
-        if (this._targetDoc[WidthSym]() < this._targetDoc?.[HeightSym]()) {
-            return (Math.min(225, this._targetDoc[HeightSym]()) * this._targetDoc[WidthSym]()) / this._targetDoc[HeightSym]();
+        if (this._targetDoc[Width]() < this._targetDoc?.[Height]()) {
+            return (Math.min(225, this._targetDoc[Height]()) * this._targetDoc[Width]()) / this._targetDoc[Height]();
         }
-        return Math.min(225, NumCast(this._targetDoc?.[WidthSym](), 225));
+        return Math.min(225, NumCast(this._targetDoc?.[Width](), 225));
     };
     height = () => {
         if (!this._targetDoc) return 225;
-        if (this._targetDoc[WidthSym]() > this._targetDoc?.[HeightSym]()) {
-            return (Math.min(225, this._targetDoc[WidthSym]()) * this._targetDoc[HeightSym]()) / this._targetDoc[WidthSym]();
+        if (this._targetDoc[Width]() > this._targetDoc?.[Height]()) {
+            return (Math.min(225, this._targetDoc[Width]()) * this._targetDoc[Height]()) / this._targetDoc[Width]();
         }
-        return Math.min(225, NumCast(this._targetDoc?.[HeightSym](), 225));
+        return Math.min(225, NumCast(this._targetDoc?.[Height](), 225));
     };
     @computed get previewHeader() {
         return !this._linkDoc || !this._markerTargetDoc || !this._targetDoc || !this._linkSrc ? null : (
diff --git a/src/client/views/nodes/MapBox/MapBox2.tsx b/src/client/views/nodes/MapBox/MapBox2.tsx
index 9354f9639..18fcc951a 100644
--- a/src/client/views/nodes/MapBox/MapBox2.tsx
+++ b/src/client/views/nodes/MapBox/MapBox2.tsx
@@ -3,7 +3,8 @@ import { Autocomplete, GoogleMap, GoogleMapProps, Marker } from '@react-google-m
 import { action, computed, IReactionDisposer, observable, ObservableMap, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Doc, DocListCast, Opt, WidthSym } from '../../../../fields/Doc';
+import { Doc, DocListCast, Opt } from '../../../../fields/Doc';
+import { Width } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkTool } from '../../../../fields/InkField';
 import { NumCast, StrCast } from '../../../../fields/Types';
@@ -367,7 +368,7 @@ export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         .ScreenToLocalTransform()
                         .scale(this.props.NativeDimScaling?.() || 1)
                         .transformDirection(delta[0], delta[1]);
-                    const fullWidth = this.layoutDoc[WidthSym]();
+                    const fullWidth = this.layoutDoc[Width]();
                     const mapWidth = fullWidth - this.sidebarWidth();
                     if (this.sidebarWidth() + localDelta[0] > 0) {
                         this._showSidebar = true;
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 95fbb274d..6ee1b67f2 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -3,7 +3,8 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction
 import { observer } from 'mobx-react';
 import * as Pdfjs from 'pdfjs-dist';
 import 'pdfjs-dist/web/pdf_viewer.css';
-import { Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../fields/Doc';
+import { Doc, DocListCast, Opt } from '../../../fields/Doc';
+import { Height, Width } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { ComputedField } from '../../../fields/ScriptField';
@@ -63,7 +64,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         super(props);
         const nw = Doc.NativeWidth(this.Document, this.dataDoc) || 927;
         const nh = Doc.NativeHeight(this.Document, this.dataDoc) || 1200;
-        !this.Document._layout_fitWidth && (this.Document._height = this.Document[WidthSym]() * (nh / nw));
+        !this.Document._layout_fitWidth && (this.Document._height = this.Document[Width]() * (nh / nw));
         if (this.pdfUrl) {
             if (PDFBox.pdfcache.get(this.pdfUrl.url.href)) runInAction(() => (this._pdf = PDFBox.pdfcache.get(this.pdfUrl!.url.href)));
             else if (PDFBox.pdfpromise.get(this.pdfUrl.url.href)) PDFBox.pdfpromise.get(this.pdfUrl.url.href)?.then(action((pdf: any) => (this._pdf = pdf)));
@@ -104,15 +105,15 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
         const docViewContent = this.props.docViewPath().lastElement().ContentDiv!;
         const newDiv = docViewContent.cloneNode(true) as HTMLDivElement;
-        newDiv.style.width = this.layoutDoc[WidthSym]().toString();
-        newDiv.style.height = this.layoutDoc[HeightSym]().toString();
+        newDiv.style.width = this.layoutDoc[Width]().toString();
+        newDiv.style.height = this.layoutDoc[Height]().toString();
         this.replaceCanvases(docViewContent, newDiv);
         const htmlString = this._pdfViewer?._mainCont.current && new XMLSerializer().serializeToString(newDiv);
 
         const anchx = NumCast(cropping.x);
         const anchy = NumCast(cropping.y);
-        const anchw = cropping[WidthSym]() * (this.props.NativeDimScaling?.() || 1);
-        const anchh = cropping[HeightSym]() * (this.props.NativeDimScaling?.() || 1);
+        const anchw = cropping[Width]() * (this.props.NativeDimScaling?.() || 1);
+        const anchh = cropping[Height]() * (this.props.NativeDimScaling?.() || 1);
         const viewScale = 1;
         cropping.title = 'crop: ' + this.rootDoc.title;
         cropping.x = NumCast(this.rootDoc.x) + NumCast(this.rootDoc._width);
@@ -169,8 +170,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             CollectionFreeFormView.UpdateIcon(
                 filename,
                 docViewContent,
-                this.layoutDoc[WidthSym](),
-                this.layoutDoc[HeightSym](),
+                this.layoutDoc[Width](),
+                this.layoutDoc[Height](),
                 this.props.PanelWidth(),
                 this.props.PanelHeight(),
                 NumCast(this.layoutDoc._layout_scrollTop),
@@ -260,8 +261,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         this.dataDoc[this.props.fieldKey + '_numPages'] = np;
         Doc.SetNativeWidth(this.dataDoc, Math.max(Doc.NativeWidth(this.dataDoc), (nw * 96) / 72));
         Doc.SetNativeHeight(this.dataDoc, (nh * 96) / 72);
-        this.layoutDoc._height = this.layoutDoc[WidthSym]() / (Doc.NativeAspect(this.dataDoc) || 1);
-        !this.Document._layout_fitWidth && (this.Document._height = this.Document[WidthSym]() * (nh / nw));
+        this.layoutDoc._height = this.layoutDoc[Width]() / (Doc.NativeAspect(this.dataDoc) || 1);
+        !this.Document._layout_fitWidth && (this.Document._height = this.Document[Width]() * (nh / nw));
     };
 
     public search = action((searchString: string, bwd?: boolean, clear: boolean = false) => {
@@ -336,7 +337,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 const ratio = (curNativeWidth + ((onButton ? 1 : -1) * localDelta[0]) / (this.props.NativeDimScaling?.() || 1)) / nativeWidth;
                 if (ratio >= 1) {
                     this.layoutDoc.nativeWidth = nativeWidth * ratio;
-                    onButton && (this.layoutDoc._width = this.layoutDoc[WidthSym]() + localDelta[0]);
+                    onButton && (this.layoutDoc._width = this.layoutDoc[Width]() + localDelta[0]);
                     this.layoutDoc._show_sidebar = nativeWidth !== this.layoutDoc._nativeWidth;
                 }
                 return false;
@@ -357,11 +358,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
         if (preview) {
             this._previewNativeWidth = nativeWidth * sideratio;
-            this._previewWidth = (this.layoutDoc[WidthSym]() * nativeWidth * sideratio) / curNativeWidth;
+            this._previewWidth = (this.layoutDoc[Width]() * nativeWidth * sideratio) / curNativeWidth;
             this._showSidebar = true;
         } else {
             this.layoutDoc.nativeWidth = nativeWidth * pdfratio;
-            this.layoutDoc._width = (this.layoutDoc[WidthSym]() * nativeWidth * pdfratio) / curNativeWidth;
+            this.layoutDoc._width = (this.layoutDoc[Width]() * nativeWidth * pdfratio) / curNativeWidth;
             this.layoutDoc._show_sidebar = nativeWidth !== this.layoutDoc._nativeWidth;
         }
     });
diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
index 80b12b96e..04f11a5df 100644
--- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
@@ -11,6 +11,7 @@ import { DocumentType } from '../../../documents/DocumentTypes';
 import { Presentation } from '../../../util/TrackMovements';
 import { Doc } from '../../../../fields/Doc';
 import { Id } from '../../../../fields/FieldSymbols';
+import { DocCast } from '../../../../fields/Types';
 
 @observer
 export class RecordingBox extends ViewBoxBaseComponent() {
@@ -57,7 +58,7 @@ export class RecordingBox extends ViewBoxBaseComponent() {
     render() {
         return (
             <div className="recordingBox" ref={this._ref}>
-                {!this.result && <RecordingView setResult={this.setResult} setDuration={this.setVideoDuration} id={this.rootDoc.proto?.[Id] || ''} />}
+                {!this.result && <RecordingView setResult={this.setResult} setDuration={this.setVideoDuration} id={DocCast(this.rootDoc.proto)?.[Id] || ''} />}
             </div>
         );
     }
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 7bf765042..1e178b123 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -5,10 +5,11 @@ import { computed, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 // import { BufferAttribute, Camera, Vector2, Vector3 } from 'three';
 import { DateField } from '../../../fields/DateField';
-import { Doc, HeightSym, WidthSym } from '../../../fields/Doc';
+import { Doc } from '../../../fields/Doc';
+import { Height, Width } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { ComputedField } from '../../../fields/ScriptField';
-import { Cast, NumCast } from '../../../fields/Types';
+import { Cast, DocCast, NumCast } from '../../../fields/Types';
 import { AudioField, VideoField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
 import { emptyFunction, returnFalse, returnOne, returnZero } from '../../../Utils';
@@ -24,6 +25,7 @@ import { FieldView, FieldViewProps } from './FieldView';
 import { FormattedTextBox } from './formattedText/FormattedTextBox';
 import './ScreenshotBox.scss';
 import { VideoBox } from './VideoBox';
+
 declare class MediaRecorder {
     constructor(e: any, options?: any); // whatever MediaRecorder has
 }
@@ -140,7 +142,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
         if (!nativeWidth || !nativeHeight) {
             if (!nativeWidth) Doc.SetNativeWidth(this.dataDoc, 1200);
             Doc.SetNativeHeight(this.dataDoc, (nativeWidth || 1200) / aspect);
-            this.layoutDoc._height = (this.layoutDoc[WidthSym]() || 0) / aspect;
+            this.layoutDoc._height = (this.layoutDoc[Width]() || 0) / aspect;
         }
     };
 
@@ -277,7 +279,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
         dictationTextProto.mediaState = ComputedField.MakeFunction('self.recordingSource.mediaState');
         this.dataDoc[this.fieldKey + '-dictation'] = dictationText;
     };
-    videoPanelHeight = () => (NumCast(this.dataDoc[this.fieldKey + '_nativeHeight'], this.layoutDoc[HeightSym]()) / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth'], this.layoutDoc[WidthSym]())) * this.props.PanelWidth();
+    videoPanelHeight = () => (NumCast(this.dataDoc[this.fieldKey + '_nativeHeight'], this.layoutDoc[Height]()) / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth'], this.layoutDoc[Width]())) * this.props.PanelWidth();
     formattedPanelHeight = () => Math.max(0, this.props.PanelHeight() - this.videoPanelHeight());
     render() {
         TraceMobx();
@@ -315,7 +317,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                         {!(this.dataDoc[this.fieldKey + '-dictation'] instanceof Doc) ? null : (
                             <FormattedTextBox
                                 {...this.props}
-                                Document={this.dataDoc[this.fieldKey + '-dictation']}
+                                Document={DocCast(this.dataDoc[this.fieldKey + '-dictation'])}
                                 fieldKey={'text'}
                                 PanelHeight={this.formattedPanelHeight}
                                 select={emptyFunction}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 9cf929679..34dcde23b 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -3,7 +3,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, untracked } from 'mobx';
 import { observer } from 'mobx-react';
 import { basename } from 'path';
-import { Doc, HeightSym, StrListCast, WidthSym } from '../../../fields/Doc';
+import { Doc, StrListCast } from '../../../fields/Doc';
+import { Height, Width } from '../../../fields/DocSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
@@ -14,6 +15,7 @@ import { Docs, DocUtils } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { Networking } from '../../Network';
 import { DocumentManager } from '../../util/DocumentManager';
+import { FollowLinkScript } from '../../util/LinkFollower';
 import { LinkManager } from '../../util/LinkManager';
 import { ReplayMovements } from '../../util/ReplayMovements';
 import { SelectionManager } from '../../util/SelectionManager';
@@ -33,8 +35,6 @@ import { FieldView, FieldViewProps } from './FieldView';
 import { RecordingBox } from './RecordingBox';
 import { PinProps, PresBox } from './trails';
 import './VideoBox.scss';
-import { ScriptField } from '../../../fields/ScriptField';
-import { FollowLinkScript } from '../../util/LinkFollower';
 const path = require('path');
 
 /**
@@ -111,7 +111,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     // returns the presentation data if it exists, null otherwise
     @computed get presentation() {
         const data = this.dataDoc[this.fieldKey + '-presentation'];
-        return data ? JSON.parse(data) : null;
+        return data ? JSON.parse(StrCast(data)) : null;
     }
 
     @computed private get timeline() {
@@ -354,8 +354,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     updateIcon = () => {
         const makeIcon = (returnedfilename: string) => {
             this.dataDoc.icon = new ImageField(returnedfilename);
-            this.dataDoc.icon_nativeWidth = this.layoutDoc[WidthSym]();
-            this.dataDoc.icon_nativeHeight = this.layoutDoc[HeightSym]();
+            this.dataDoc.icon_nativeWidth = this.layoutDoc[Width]();
+            this.dataDoc.icon_nativeHeight = this.layoutDoc[Height]();
         };
         this.Snapshot(undefined, undefined, makeIcon);
     };
@@ -465,7 +465,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                 { fireImmediately: true }
             );
 
-            (!this.dataDoc[this.fieldKey + '_thumbnails'] || this.dataDoc[this.fieldKey + '_thumbnails'].length != VideoBox.numThumbnails) && this.getVideoThumbnails();
+            (!this.dataDoc[this.fieldKey + '_thumbnails'] || StrListCast(this.dataDoc[this.fieldKey + '_thumbnails']).length != VideoBox.numThumbnails) && this.getVideoThumbnails();
         }
     };
 
@@ -870,7 +870,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                 }),
                 returnFalse,
                 () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations),
-                false, false
+                false,
+                false
             );
         }
     };
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index a898398a1..48f77469f 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -2,16 +2,18 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as WebRequest from 'web-request';
-import { Doc, DocListCast, Field, HeightSym, Opt, WidthSym } from '../../../fields/Doc';
+import { Doc, DocListCast, Field, Opt } from '../../../fields/Doc';
+import { Height, Width } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { HtmlField } from '../../../fields/HtmlField';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
+import { RefField } from '../../../fields/RefField';
 import { listSpec } from '../../../fields/Schema';
 import { Cast, ImageCast, NumCast, StrCast, WebCast } from '../../../fields/Types';
 import { ImageField, WebField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, getWordAtPoint, removeStyleSheetRule, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents, smoothScroll, StopEvent, Utils } from '../../../Utils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, getWordAtPoint, returnFalse, returnOne, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager } from '../../util/DragManager';
@@ -37,7 +39,6 @@ import { LinkDocPreview } from './LinkDocPreview';
 import { PinProps, PresBox } from './trails';
 import './WebBox.scss';
 import React = require('react');
-import { RefField } from '../../../fields/RefField';
 const { CreateImage } = require('./WebBoxRenderer');
 const _global = (window /* browser */ || global) /* node */ as any;
 const htmlToText = require('html-to-text');
@@ -84,7 +85,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return this._url ? WebBox.urlHash(this._url) + '' : '';
     }
     @computed get scrollHeight() {
-        return Math.max(this.layoutDoc[HeightSym](), this._scrollHeight);
+        return Math.max(this.layoutDoc[Height](), this._scrollHeight);
     }
     @computed get allAnnotations() {
         return DocListCast(this.dataDoc[this.annotationKey]);
@@ -214,7 +215,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 if (!nativeWidth || !nativeHeight || Math.abs(nativeWidth / nativeHeight - youtubeaspect) > 0.05) {
                     if (!nativeWidth) Doc.SetNativeWidth(this.layoutDoc, 600);
                     Doc.SetNativeHeight(this.layoutDoc, (nativeWidth || 600) / youtubeaspect);
-                    this.layoutDoc._height = this.layoutDoc[WidthSym]() / youtubeaspect;
+                    this.layoutDoc._height = this.layoutDoc[Width]() / youtubeaspect;
                 }
             } // else it's an HTMLfield
         } else if (this.webField && !this.dataDoc.text) {
@@ -281,7 +282,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     focus = (anchor: Doc, options: DocFocusOptions) => {
         if (anchor !== this.rootDoc && this._outerRef.current) {
             const windowHeight = this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
-            const scrollTo = Utils.scrollIntoView(NumCast(anchor.y), anchor[HeightSym](), NumCast(this.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, Math.max(NumCast(anchor.y) + anchor[HeightSym](), this._scrollHeight));
+            const scrollTo = Utils.scrollIntoView(NumCast(anchor.y), anchor[Height](), NumCast(this.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, Math.max(NumCast(anchor.y) + anchor[Height](), this._scrollHeight));
             if (scrollTo !== undefined) {
                 if (this._initialScroll === undefined) {
                     const focusTime = options.zoomTime ?? 500;
@@ -460,7 +461,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 this._scrollHeight = Math.max(this._scrollHeight, (iframeContent.body.children[0] as any)?.scrollHeight || 0);
                 if (this._scrollHeight) {
                     this.rootDoc.nativeHeight = Math.min(NumCast(this.rootDoc.nativeHeight), this._scrollHeight);
-                    this.layoutDoc.height = Math.min(this.layoutDoc[HeightSym](), (this.layoutDoc[WidthSym]() * NumCast(this.rootDoc.nativeHeight)) / NumCast(this.rootDoc.nativeWidth));
+                    this.layoutDoc.height = Math.min(this.layoutDoc[Height](), (this.layoutDoc[Width]() * NumCast(this.rootDoc.nativeHeight)) / NumCast(this.rootDoc.nativeWidth));
                 }
             };
             initHeights();
@@ -784,7 +785,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 if (ratio >= 1) {
                     this.layoutDoc.nativeWidth = nativeWidth * ratio;
                     this.layoutDoc.nativeHeight = nativeHeight * (1 + ratio);
-                    onButton && (this.layoutDoc._width = this.layoutDoc[WidthSym]() + localDelta[0]);
+                    onButton && (this.layoutDoc._width = this.layoutDoc[Width]() + localDelta[0]);
                     this.layoutDoc._layout_showSidebar = nativeWidth !== this.layoutDoc._nativeWidth;
                 }
                 return false;
@@ -820,9 +821,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     toggleSidebar = action((preview: boolean = false) => {
         var nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']);
         if (!nativeWidth) {
-            const defaultNativeWidth = this.rootDoc[this.fieldKey] instanceof WebField ? 850 : this.Document[WidthSym]();
+            const defaultNativeWidth = this.rootDoc[this.fieldKey] instanceof WebField ? 850 : this.Document[Width]();
             Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(this.dataDoc) || defaultNativeWidth);
-            Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(this.dataDoc) || (this.Document[HeightSym]() / this.Document[WidthSym]()) * defaultNativeWidth);
+            Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(this.dataDoc) || (this.Document[Height]() / this.Document[Width]()) * defaultNativeWidth);
             nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']);
         }
         const sideratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? WebBox.openSidebarWidth : 0) + nativeWidth) / nativeWidth;
@@ -830,11 +831,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
         if (preview) {
             this._previewNativeWidth = nativeWidth * sideratio;
-            this._previewWidth = (this.layoutDoc[WidthSym]() * nativeWidth * sideratio) / curNativeWidth;
+            this._previewWidth = (this.layoutDoc[Width]() * nativeWidth * sideratio) / curNativeWidth;
             this._showSidebar = true;
         } else {
             this.layoutDoc._layout_showSidebar = !this.layoutDoc._layout_showSidebar;
-            this.layoutDoc._width = (this.layoutDoc[WidthSym]() * nativeWidth * pdfratio) / curNativeWidth;
+            this.layoutDoc._width = (this.layoutDoc[Width]() * nativeWidth * pdfratio) / curNativeWidth;
             if (!this.layoutDoc._layout_showSidebar && !(this.dataDoc[this.fieldKey] instanceof WebField)) {
                 this.layoutDoc.nativeWidth = this.dataDoc[this.fieldKey + '_nativeWidth'] = undefined;
             } else {
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index fb29f95f4..7f08de246 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -5,7 +5,8 @@ import { action, computed, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { ColorState, SketchPicker } from 'react-color';
-import { Doc, HeightSym, StrListCast, WidthSym } from '../../../../fields/Doc';
+import { Doc, StrListCast } from '../../../../fields/Doc';
+import { Height, Width } from '../../../../fields/DocSymbols';
 import { InkTool } from '../../../../fields/InkField';
 import { ScriptField } from '../../../../fields/ScriptField';
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
@@ -757,8 +758,8 @@ export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) {
                 action(d => {
                     const x = NumCast(d.x);
                     const y = NumCast(d.y);
-                    const width = d[WidthSym]();
-                    const height = d[HeightSym]();
+                    const width = d[Width]();
+                    const height = d[Height]();
                     bounds.push({ x, y, width, height });
                 })
             );
@@ -795,8 +796,8 @@ export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) {
             // TODO: nda - this is the code to actually get a new grouped collection
             const newCollection = marqViewRef?.getCollection(selected, undefined, true);
             if (newCollection) {
-                newCollection.height = newCollection[HeightSym]();
-                newCollection.width = newCollection[WidthSym]();
+                newCollection.height = newCollection[Height]();
+                newCollection.width = newCollection[Width]();
             }
 
             // nda - bug: when deleting a stroke before leaving writing mode, delete the stroke from unprocessed ink docs
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index c929b7ff3..1d41eb379 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -2,8 +2,9 @@ import { action, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import { NodeSelection } from 'prosemirror-state';
 import * as ReactDOM from 'react-dom/client';
-import { Doc, HeightSym, WidthSym } from '../../../../fields/Doc';
-import { Cast, NumCast, StrCast } from '../../../../fields/Types';
+import { Doc } from '../../../../fields/Doc';
+import { Height, Width } from '../../../../fields/DocSymbols';
+import { NumCast, StrCast } from '../../../../fields/Types';
 import { emptyFunction, returnFalse, Utils } from '../../../../Utils';
 import { DocServer } from '../../../DocServer';
 import { Docs, DocUtils } from '../../../documents/Documents';
@@ -210,8 +211,8 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
                     addDocTab={this._textBox.props.addDocTab}
                     pinToPres={returnFalse}
                     renderDepth={this._textBox.props.renderDepth + 1}
-                    PanelWidth={this._finalLayout[WidthSym]}
-                    PanelHeight={this._finalLayout[HeightSym]}
+                    PanelWidth={this._finalLayout[Width]}
+                    PanelHeight={this._finalLayout[Height]}
                     focus={this.outerFocus}
                     whenChildContentsActiveChanged={returnFalse}
                     bringToFront={emptyFunction}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index fb0c0d2ab..bea789a5c 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -12,9 +12,11 @@ import { Fragment, Mark, Node, Slice } from 'prosemirror-model';
 import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from 'prosemirror-state';
 import { EditorView } from 'prosemirror-view';
 import { DateField } from '../../../../fields/DateField';
-import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, CssSym, Doc, DocListCast, Field, ForceServerWrite, HeightSym, Opt, StrListCast, UpdatingFromServer, WidthSym } from '../../../../fields/Doc';
+import { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc';
+import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, ForceServerWrite, Height, UpdatingFromServer, Width } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkTool } from '../../../../fields/InkField';
+import { List } from '../../../../fields/List';
 import { PrefetchProxy } from '../../../../fields/Proxy';
 import { RichTextField } from '../../../../fields/RichTextField';
 import { RichTextUtils } from '../../../../fields/RichTextUtils';
@@ -34,6 +36,7 @@ import { DragManager } from '../../../util/DragManager';
 import { MakeTemplate } from '../../../util/DropConverter';
 import { IsFollowLinkScript } from '../../../util/LinkFollower';
 import { LinkManager } from '../../../util/LinkManager';
+import { RTFMarkup } from '../../../util/RTFMarkup';
 import { SelectionManager } from '../../../util/SelectionManager';
 import { SnappingManager } from '../../../util/SnappingManager';
 import { undoBatch, UndoManager } from '../../../util/UndoManager';
@@ -68,8 +71,6 @@ import { schema } from './schema_rts';
 import { SummaryView } from './SummaryView';
 import applyDevTools = require('prosemirror-dev-tools');
 import React = require('react');
-import { RTFMarkup } from '../../../util/RTFMarkup';
-import { List } from '../../../../fields/List';
 const translateGoogleApi = require('translate-google-api');
 export const GoogleRef = 'googleDocId';
 type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void;
@@ -544,8 +545,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             } else if (de.embedKey) {
                 const target = dragData.droppedDocuments[0];
                 const node = schema.nodes.dashDoc.create({
-                    width: target[WidthSym](),
-                    height: target[HeightSym](),
+                    width: target[Width](),
+                    height: target[Height](),
                     title: 'dashDoc',
                     docId: target[Id],
                     float: 'unset',
@@ -646,7 +647,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             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() }));
         }
-        this.layoutDoc[CssSym] = this.layoutDoc[CssSym] + 1; // css changes happen outside of react/mobx.  so we need to set a flag that will notify anyone intereted in layout changes triggered by css changes (eg., CollectionLinkView)
+        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 intereted in layout changes triggered by css changes (eg., CollectionLinkView)
     };
 
     @observable _showSidebar = false;
@@ -682,7 +683,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             .scale(this.props.NativeDimScaling?.() || 1)
             .transformDirection(delta[0], delta[1]);
         const sidebarWidth = (NumCast(this.layoutDoc._width) * Number(this.layout_sidebarWidthPercent.replace('%', ''))) / 100;
-        const width = this.layoutDoc[WidthSym]() + localDelta[0];
+        const width = this.layoutDoc[Width]() + localDelta[0];
         this.layoutDoc._layout_sidebarWidthPercent = Math.max(0, (sidebarWidth + localDelta[0]) / width) * 100 + '%';
         this.layoutDoc.width = width;
         this.layoutDoc._layout_showSidebar = this.layoutDoc._layout_sidebarWidthPercent !== '0%';
@@ -1163,7 +1164,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             () => {
                 const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc?.proto), this.fieldKey) ? DocCast(this.layoutDoc?.proto) : this?.dataDoc;
                 const whichDoc = !this.dataDoc || !this.layoutDoc ? undefined : dataDoc?.[this.fieldKey + '_noTemplate'] || !this.layoutDoc[this.fieldKey] ? dataDoc : this.layoutDoc;
-                return !whichDoc ? undefined : { data: Cast(whichDoc[this.fieldKey], RichTextField, null), str: Field.toString(whichDoc[this.fieldKey]) };
+                return !whichDoc ? undefined : { data: Cast(whichDoc[this.fieldKey], RichTextField, null), str: Field.toString(DocCast(whichDoc[this.fieldKey])) };
             },
             incomingValue => {
                 if (this._editorView && this._applyingChange !== this.fieldKey) {
@@ -1208,7 +1209,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             () => this.props.isSelected(),
             action(selected => {
                 if (FormattedTextBox._globalHighlights.has('Bold Text')) {
-                    this.layoutDoc[CssSym] = this.layoutDoc[CssSym] + 1; // css change happens outside of mobx/react, so this will notify anyone interested in the layout that it has changed
+                    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 && !selected) {
                     RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined);
@@ -2000,14 +2001,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         return this.props.fieldKey + (usePath && (!usePath.includes(':hover') || this._isHovering) ? `_${usePath.replace(':hover', '')}` : '');
     }
     @observable _isHovering = false;
-    onPassiveWheel = (e:WheelEvent) => { 
+    onPassiveWheel = (e: WheelEvent) => {
         // 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() && !this.props.allowScroll) {
             if (!NumCast(this.layoutDoc._layout_scrollTop) && e.deltaY <= 0) e.preventDefault();
             e.stopPropagation();
         }
-    }
-    _oldWheel:any;
+    };
+    _oldWheel: any;
     render() {
         TraceMobx();
         const active = this.props.isContentActive() || this.props.isSelected();
@@ -2029,8 +2030,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 onPointerLeave={action(() => (this._isHovering = false))}
                 ref={r => {
                     this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
-                    this._oldWheel= r;
-                    r?.addEventListener('wheel', this.onPassiveWheel,  { passive: false } );
+                    this._oldWheel = r;
+                    r?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
                 }}
                 style={{
                     ...(this.props.dontScale
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index f197a8a8d..154c879a0 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -2,12 +2,13 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@material-ui/core';
 import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
-import { Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../../fields/Doc';
+import { Doc, DocListCast, Opt } from '../../../../fields/Doc';
+import { Height, Width } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { List } from '../../../../fields/List';
 import { Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
 import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils';
-import { Docs, DocUtils } from '../../../documents/Documents';
+import { Docs } from '../../../documents/Documents';
 import { CollectionViewType } from '../../../documents/DocumentTypes';
 import { DocumentManager } from '../../../util/DocumentManager';
 import { DragManager } from '../../../util/DragManager';
@@ -322,7 +323,9 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     };
 
     removeAllRecordingInOverlay = () => {
-        DocListCast(Doc.MyOverlayDocs.data).filter(doc => doc.slides === this.rootDoc).forEach(Doc.RemFromMyOverlay);
+        DocListCast(Doc.MyOverlayDocs.data)
+            .filter(doc => doc.slides === this.rootDoc)
+            .forEach(Doc.RemFromMyOverlay);
     };
 
     static removeEveryExistingRecordingInOverlay = () => {
@@ -386,8 +389,8 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
             activeItem.recording = recording;
 
             // make recording box appear in the bottom right corner of the screen
-            recording.overlayX = window.innerWidth - recording[WidthSym]() - 20;
-            recording.overlayY = window.innerHeight - recording[HeightSym]() - 20;
+            recording.overlayX = window.innerWidth - recording[Width]() - 20;
+            recording.overlayY = window.innerHeight - recording[Height]() - 20;
             Doc.AddToMyOverlay(recording);
         }
     };
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 999e3eeb6..bc0cad636 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -2,7 +2,8 @@ import { action, computed, IReactionDisposer, observable, ObservableMap, reactio
 import { observer } from 'mobx-react';
 import * as Pdfjs from 'pdfjs-dist';
 import 'pdfjs-dist/web/pdf_viewer.css';
-import { Doc, DocListCast, Field, HeightSym, Opt } from '../../../fields/Doc';
+import { Doc, DocListCast, Field, Opt } from '../../../fields/Doc';
+import { Height } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { Cast, NumCast, StrCast } from '../../../fields/Types';
@@ -181,7 +182,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
         let focusSpeed: Opt<number>;
         if (doc !== this.props.rootDoc && mainCont) {
             const windowHeight = this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
-            const scrollTo = Utils.scrollIntoView(scrollTop, doc[HeightSym](), NumCast(this.props.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, this._scrollHeight);
+            const scrollTo = Utils.scrollIntoView(scrollTop, doc[Height](), NumCast(this.props.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, this._scrollHeight);
             if (scrollTo !== undefined && scrollTo !== this.props.layoutDoc._layout_scrollTop) {
                 if (!this._pdfViewer) this._initialScroll = { loc: scrollTo, easeFunc: options.easeFunc };
                 else if (!options.instant) this._scrollStopper = smoothScroll((focusSpeed = options.zoomTime ?? 500), mainCont, scrollTo, options.easeFunc, this._scrollStopper);
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index d0e5d03b3..72eeedcff 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -11,9 +11,38 @@ import { SelectionManager } from '../client/util/SelectionManager';
 import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from '../client/util/SerializationHelper';
 import { undoable, UndoManager } from '../client/util/UndoManager';
 import { decycle } from '../decycler/decycler';
+import * as JSZipUtils from '../JSZipUtils';
 import { DashColor, incrementTitleCopy, intersectRect, Utils } from '../Utils';
 import { DateField } from './DateField';
-import { Copy, HandleUpdate, Id, OnUpdate, Parent, Self, SelfProxy, ToScriptString, ToString, Update } from './FieldSymbols';
+import {
+    AclAdmin,
+    AclAugment,
+    AclEdit,
+    AclPrivate,
+    AclReadonly,
+    AclSelfEdit,
+    AclUnset,
+    Animation,
+    CachedUpdates,
+    DirectLinks,
+    DocAcl,
+    DocCss,
+    DocData,
+    DocFields,
+    DocLayout,
+    FieldKeys,
+    FieldTuples,
+    ForceServerWrite,
+    Height,
+    Highlight,
+    Initializing,
+    Self,
+    SelfProxy,
+    Update,
+    UpdatingFromServer,
+    Width,
+} from './DocSymbols';
+import { Copy, HandleUpdate, Id, OnUpdate, Parent, ToScriptString, ToString } from './FieldSymbols';
 import { InkField, InkTool } from './InkField';
 import { List, ListFieldName } from './List';
 import { ObjectField } from './ObjectField';
@@ -26,7 +55,6 @@ import { Cast, DocCast, FieldValue, NumCast, StrCast, ToConstructor } from './Ty
 import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField } from './URLField';
 import { deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, updateFunction } from './util';
 import JSZip = require('jszip');
-import * as JSZipUtils from '../JSZipUtils';
 export namespace Field {
     export function toKeyValueString(doc: Doc, key: string): string {
         const onDelegate = Object.keys(doc).includes(key.replace(/^_/, ''));
@@ -93,28 +121,6 @@ export function DocListCast(field: FieldResult, defaultVal: Doc[] = []) {
     return Cast(field, listSpec(Doc), defaultVal).filter(d => d instanceof Doc) as Doc[];
 }
 
-export const WidthSym = Symbol('Width');
-export const HeightSym = Symbol('Height');
-export const AnimationSym = Symbol('Animation');
-export const HighlightSym = Symbol('Highlight');
-export const DataSym = Symbol('Data');
-export const LayoutSym = Symbol('Layout');
-export const FieldsSym = Symbol('Fields');
-export const CssSym = Symbol('Css');
-export const AclSym = Symbol('Acl');
-export const DirectLinksSym = Symbol('DirectLinks');
-export const AclUnset = Symbol('AclUnset');
-export const AclPrivate = Symbol('AclOwnerOnly');
-export const AclReadonly = Symbol('AclReadOnly');
-export const AclAugment = Symbol('AclAugment');
-export const AclSelfEdit = Symbol('AclSelfEdit');
-export const AclEdit = Symbol('AclEdit');
-export const AclAdmin = Symbol('AclAdmin');
-export const UpdatingFromServer = Symbol('UpdatingFromServer');
-export const Initializing = Symbol('Initializing');
-export const ForceServerWrite = Symbol('ForceServerWrite');
-export const CachedUpdates = Symbol('Cached updates');
-
 export enum aclLevel {
     unset = -1,
     unshared = 0,
@@ -141,15 +147,15 @@ export const ReverseHierarchyMap: Map<string, { level: aclLevel; acl: symbol }>
 export function updateCachedAcls(doc: Doc) {
     if (!doc) return;
 
-    const target = (doc as any)?.__fields ?? doc;
+    const target = (doc as any)?.__fieldTuples ?? doc;
     const permissions: { [key: string]: symbol } = !target.author || target.author === Doc.CurrentUserEmail ? { 'acl-Me': AclAdmin } : {};
     Object.keys(target).filter(key => key.startsWith('acl') && (permissions[key] = ReverseHierarchyMap.get(StrCast(target[key]))!.acl));
-    if (Object.keys(permissions).length || doc[AclSym]?.length) {
-        runInAction(() => (doc[AclSym] = permissions));
+    if (Object.keys(permissions).length || doc[DocAcl]?.length) {
+        runInAction(() => (doc[DocAcl] = permissions));
     }
 
     if (doc.proto instanceof Promise) {
-        doc.proto.then(updateCachedAcls);
+        doc.proto.then(proto => updateCachedAcls(DocCast(proto)));
         return doc.proto;
     }
 }
@@ -295,25 +301,20 @@ export class Doc extends RefField {
             set: setter,
             get: getter,
             // getPrototypeOf: (target) => Cast(target[SelfProxy].proto, Doc) || null, // TODO this might be able to replace the proto logic in getter
-            has: (target, key) => GetEffectiveAcl(target) !== AclPrivate && key in target.__fields,
+            has: (target, key) => GetEffectiveAcl(target) !== AclPrivate && key in target.__fieldTuples,
             ownKeys: target => {
-                const obj = {} as any;
-                if (GetEffectiveAcl(target) !== AclPrivate) Object.assign(obj, target.___fieldKeys);
-                runInAction(() => (obj.__LAYOUT__ = target.__LAYOUT__));
-                return Object.keys(obj);
+                const keys = GetEffectiveAcl(target) !== AclPrivate ? Object.keys(target[FieldKeys]) : [];
+                return [...keys, '__LAYOUT__'];
             },
             getOwnPropertyDescriptor: (target, prop) => {
-                if (prop.toString() === '__LAYOUT__') {
+                if (prop.toString() === '__LAYOUT__' || !(prop in target[FieldKeys])) {
                     return Reflect.getOwnPropertyDescriptor(target, prop);
                 }
-                if (prop in target.__fieldKeys) {
-                    return {
-                        configurable: true, //TODO Should configurable be true?
-                        enumerable: true,
-                        value: 0, //() => target.__fields[prop])
-                    };
-                }
-                return Reflect.getOwnPropertyDescriptor(target, prop);
+                return {
+                    configurable: true, //TODO Should configurable be true?
+                    enumerable: true,
+                    value: 0, //() => target.__fieldTuples[prop])
+                };
             },
             deleteProperty: deleteProperty,
             defineProperty: () => {
@@ -327,41 +328,35 @@ export class Doc extends RefField {
         return docProxy;
     }
 
-    proto: Opt<Doc>;
     [key: string]: FieldResult;
 
     @serializable(alias('fields', map(autoObject(), { afterDeserialize: afterDocDeserialize })))
-    private get __fields() {
-        return this.___fields;
+    private get __fieldTuples() {
+        return this[FieldTuples];
     }
-    private set __fields(value) {
-        this.___fields = value;
+    private set __fieldTuples(value) {
+        // called by deserializer to set all fields in one shot
+        this[FieldTuples] = value;
         for (const key in value) {
             const field = value[key];
-            field !== undefined && (this.__fieldKeys[key] = true);
+            field !== undefined && (this[FieldKeys][key] = true);
             if (!(field instanceof ObjectField)) continue;
             field[Parent] = this[Self];
             field[OnUpdate] = updateFunction(this[Self], key, field, this[SelfProxy]);
         }
     }
-    private get __fieldKeys() {
-        return this.___fieldKeys;
-    }
-    private set __fieldKeys(value) {
-        this.___fieldKeys = value;
-    }
 
-    @observable private ___fields: any = {};
-    @observable private ___fieldKeys: any = {};
+    @observable private [FieldTuples]: any = {};
+    @observable private [FieldKeys]: any = {};
     /// all of the raw acl's that have been set on this document.  Use GetEffectiveAcl to determine the actual ACL of the doc for editing
-    @observable public [AclSym]: { [key: string]: symbol } = {};
-    @observable public [CssSym]: number = 0; // incrementer denoting a change to CSS layout
-    @observable public [DirectLinksSym]: Set<Doc> = new Set();
-    @observable public [AnimationSym]: Opt<Doc>;
-    @observable public [HighlightSym]: boolean = false;
+    @observable public [DocAcl]: { [key: string]: symbol } = {};
+    @observable public [DocCss]: number = 0; // incrementer denoting a change to CSS layout
+    @observable public [DirectLinks]: Set<Doc> = new Set();
+    @observable public [Animation]: Opt<Doc>;
+    @observable public [Highlight]: boolean = false;
     static __Anim(Doc: Doc) {
         // for debugging to print AnimationSym field easily.
-        return Doc[AnimationSym];
+        return Doc[Animation];
     }
 
     private [UpdatingFromServer]: boolean = false;
@@ -374,15 +369,15 @@ export class Doc extends RefField {
 
     private [Self] = this;
     private [SelfProxy]: any;
-    public [FieldsSym] = () => this[Self].___fields; // Object.keys(this).reduce((fields, key) => { fields[key] = this[key]; return fields; }, {} as any);
-    public [WidthSym] = () => NumCast(this[SelfProxy]._width);
-    public [HeightSym] = () => NumCast(this[SelfProxy]._height);
+    public [DocFields] = () => this[Self][FieldTuples]; // Object.keys(this).reduce((fields, key) => { fields[key] = this[key]; return fields; }, {} as any);
+    public [Width] = () => NumCast(this[SelfProxy]._width);
+    public [Height] = () => NumCast(this[SelfProxy]._height);
     public [ToScriptString] = () => `idToDoc("${this[Self][Id]}")`;
     public [ToString] = () => `Doc(${GetEffectiveAcl(this[SelfProxy]) === AclPrivate ? '-inaccessible-' : this[SelfProxy].title})`;
-    public get [LayoutSym]() {
+    public get [DocLayout]() {
         return this[SelfProxy].__LAYOUT__;
     }
-    public get [DataSym](): Doc {
+    public get [DocData](): Doc {
         const self = this[SelfProxy];
         return self.resolvedDataDoc && !self.isTemplateForField ? self : Doc.GetProto(Cast(Doc.Layout(self).resolvedDataDoc, Doc, null) || self);
     }
@@ -535,17 +530,17 @@ export namespace Doc {
         if (key.startsWith('_')) key = key.substring(1);
         const hasProto = Doc.GetProto(doc) !== doc ? Doc.GetProto(doc) : undefined;
         const onDeleg = Object.getOwnPropertyNames(doc).indexOf(key) !== -1;
-        const onProto = hasProto && Object.getOwnPropertyNames(doc.proto).indexOf(key) !== -1;
+        const onProto = hasProto && Object.getOwnPropertyNames(hasProto).indexOf(key) !== -1;
         if (onDeleg || !hasProto || (!onProto && !defaultProto)) {
             doc[key] = value;
-        } else doc.proto![key] = value;
+        } else hasProto[key] = value;
     }
     export function GetAllPrototypes(doc: Doc): Doc[] {
         const protos: Doc[] = [];
         let d: Opt<Doc> = doc;
         while (d) {
             protos.push(d);
-            d = FieldValue(d.proto);
+            d = DocCast(FieldValue(d.proto));
         }
         return protos;
     }
@@ -585,7 +580,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 : 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 {
@@ -599,7 +594,7 @@ export namespace Doc {
         let proto: Doc | undefined = doc;
         while (proto) {
             Object.keys(proto).forEach(key => results.add(key));
-            proto = proto.proto;
+            proto = DocCast(FieldValue(proto.proto));
         }
 
         return Array.from(results);
@@ -676,7 +671,7 @@ export namespace Doc {
         const bounds = docList.reduce(
             (bounds, doc) => {
                 const [sptX, sptY] = [NumCast(doc.x), NumCast(doc.y)];
-                const [bptX, bptY] = [sptX + doc[WidthSym](), sptY + doc[HeightSym]()];
+                const [bptX, bptY] = [sptX + doc[Width](), sptY + doc[Height]()];
                 return {
                     x: Math.min(sptX, bounds.x),
                     y: Math.min(sptY, bounds.y),
@@ -700,7 +695,7 @@ export namespace Doc {
         embedding.title = ComputedField.MakeFunction(`renameEmbedding(this)`);
         embedding.author = Doc.CurrentUserEmail;
 
-        Doc.AddDocToList(Doc.GetProto(doc)[DataSym], 'proto_embeddings', embedding);
+        Doc.AddDocToList(Doc.GetProto(doc)[DocData], 'proto_embeddings', embedding);
 
         return embedding;
     }
@@ -772,7 +767,7 @@ export namespace Doc {
                 }
             })
         );
-        Array.from(doc[DirectLinksSym]).forEach(async link => {
+        Array.from(doc[DirectLinks]).forEach(async link => {
             if (
                 cloneLinks ||
                 ((cloneMap.has(DocCast(link.link_anchor_1)?.[Id]) || cloneMap.has(DocCast(DocCast(link.link_anchor_1)?.annotationOn)?.[Id])) &&
@@ -930,7 +925,7 @@ export namespace Doc {
             if (templateLayoutDoc.resolvedDataDoc === (targetDoc.rootDocument || Doc.GetProto(targetDoc))) {
                 expandedTemplateLayout = templateLayoutDoc; // reuse an existing template layout if its for the same document with the same params
             } else {
-                templateLayoutDoc.resolvedDataDoc && (templateLayoutDoc = Cast(templateLayoutDoc.proto, Doc, null) || templateLayoutDoc); // if the template has already been applied (ie, a nested template), then use the template's prototype
+                templateLayoutDoc.resolvedDataDoc && (templateLayoutDoc = DocCast(templateLayoutDoc.proto, templateLayoutDoc)); // if the template has already been applied (ie, a nested template), then use the template's prototype
                 if (!targetDoc[expandedLayoutFieldKey]) {
                     _pendingMap.set(targetDoc[Id] + expandedLayoutFieldKey, true);
                     setTimeout(
@@ -969,7 +964,7 @@ export namespace Doc {
             const field = ProxyField.WithoutProxy(() => doc[key]);
             if (key === 'proto' && copyProto) {
                 if (doc.proto instanceof Doc && overwrite.proto instanceof Doc) {
-                    overwrite[key] = Doc.Overwrite(doc[key]!, overwrite.proto);
+                    overwrite[key] = Doc.Overwrite(doc.proto, overwrite.proto);
                 }
             } else {
                 if (field instanceof RefField) {
@@ -991,38 +986,37 @@ export namespace Doc {
         const copy = new Doc(copyProtoId, true);
         updateCachedAcls(copy);
         const exclude = [...StrListCast(doc.cloneFieldFilter), 'dragFactory_count', 'cloneFieldFilter'];
-        Object.keys(doc).forEach(key => {
-            if (exclude.includes(key)) return;
-            const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
-            const field = key === 'author' ? Doc.CurrentUserEmail : ProxyField.WithoutProxy(() => doc[key]);
-            if (key === 'proto' && copyProto) {
-                if (doc[key] instanceof Doc) {
-                    copy[key] = Doc.MakeCopy(doc[key]!, false);
-                }
-            } else {
-                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) {
-                    copy[key] =
-                        doc[key] instanceof Doc
-                            ? key.includes('layout[')
-                                ? undefined
-                                : doc[key] // reference documents except remove documents that are expanded teplate fields
-                            : ObjectField.MakeCopy(field);
-                } else if (field instanceof Promise) {
-                    debugger; //This shouldn't happend...
+        Object.keys(doc)
+            .filter(key => !exclude.includes(key))
+            .forEach(key => {
+                if (key === 'proto' && copyProto) {
+                    if (doc.proto instanceof Doc) {
+                        copy[key] = Doc.MakeCopy(doc.proto, false);
+                    }
                 } else {
-                    copy[key] = field;
+                    const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
+                    const field = key === 'author' ? Doc.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) {
+                        copy[key] =
+                            doc[key] instanceof Doc && key.includes('layout[')
+                                ? undefined // remove expanded template field documents
+                                : ObjectField.MakeCopy(field);
+                    } else if (field instanceof Promise) {
+                        debugger; //This shouldn't happend...
+                    } else {
+                        copy[key] = field;
+                    }
                 }
-            }
-        });
+            });
         if (copyProto) {
             Doc.GetProto(copy).embedContainer = undefined;
             Doc.GetProto(copy).proto_embeddings = new List<Doc>([copy]);
         } else {
-            Doc.AddDocToList(Doc.GetProto(copy)[DataSym], 'proto_embeddings', copy);
+            Doc.AddDocToList(Doc.GetProto(copy)[DocData], 'proto_embeddings', copy);
         }
         copy.embedContainer = undefined;
         Doc.defaultAclPrivate && (copy['acl-Public'] = 'Not Shared');
@@ -1045,7 +1039,7 @@ export namespace Doc {
             Object.keys(doc)
                 .filter(key => key.startsWith('acl'))
                 .forEach(key => (delegate[key] = doc[key]));
-            if (!Doc.IsSystem(doc)) Doc.AddDocToList(doc[DataSym], 'proto_embeddings', delegate);
+            if (!Doc.IsSystem(doc)) Doc.AddDocToList(doc[DocData], 'proto_embeddings', delegate);
             title && (delegate.title = title);
             delegate[Initializing] = false;
             Doc.AddFileOrphan(delegate);
@@ -1069,7 +1063,7 @@ export namespace Doc {
         delegate[Initializing] = true;
         delegate.proto = delegateProto;
         delegate.author = Doc.CurrentUserEmail;
-        Doc.AddDocToList(delegateProto[DataSym], 'proto_embeddings', delegate);
+        Doc.AddDocToList(delegateProto[DocData], 'proto_embeddings', delegate);
         delegate[Initializing] = false;
         delegateProto[Initializing] = false;
         return delegate;
@@ -1173,7 +1167,7 @@ export namespace Doc {
     }
     export const brushManager = new DocBrush();
 
-    export class DocData {
+    export class UserDocData {
         @observable _user_doc: Doc = undefined!;
         @observable _sharing_doc: Doc = undefined!;
         @observable _searchQuery: string = '';
@@ -1183,7 +1177,7 @@ export namespace Doc {
     // a layout field or 'layout' is given.
     export function Layout(doc: Doc, layout?: Doc): Doc {
         const overrideLayout = layout && Cast(doc[`${StrCast(layout.isTemplateForField, 'data')}-layout[` + layout[Id] + ']'], Doc, null);
-        return overrideLayout || doc[LayoutSym] || doc;
+        return overrideLayout || doc[DocLayout] || doc;
     }
     export function SetLayout(doc: Doc, layout: Doc | string) {
         doc[StrCast(doc.layout_fieldKey, 'layout')] = layout;
@@ -1198,12 +1192,12 @@ 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.LayoutFieldKey(doc) + '_nativeWidth'], useWidth ? doc[WidthSym]() : 0));
+        return !doc ? 0 : NumCast(doc._nativeWidth, NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '_nativeWidth'], useWidth ? doc[Width]() : 0));
     }
     export function NativeHeight(doc?: Doc, dataDoc?: Doc, useHeight?: boolean) {
         if (!doc) return 0;
-        const nheight = (Doc.NativeWidth(doc, dataDoc, useHeight) * doc[HeightSym]()) / doc[WidthSym]();
-        const dheight = NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '_nativeHeight'], useHeight ? doc[HeightSym]() : 0);
+        const nheight = (Doc.NativeWidth(doc, dataDoc, useHeight) * doc[Height]()) / doc[Width]();
+        const dheight = NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '_nativeHeight'], useHeight ? doc[Height]() : 0);
         return NumCast(doc._nativeHeight, nheight || dheight);
     }
     export function SetNativeWidth(doc: Doc, width: number | undefined, fieldKey?: string) {
@@ -1213,7 +1207,7 @@ export namespace Doc {
         doc[(fieldKey ?? Doc.LayoutFieldKey(doc)) + '_nativeHeight'] = height;
     }
 
-    const manager = new DocData();
+    const manager = new UserDocData();
     export function SearchQuery(): string {
         return manager._searchQuery;
     }
@@ -1348,16 +1342,16 @@ export namespace Doc {
     export var highlightedDocs = new ObservableSet<Doc>();
     export function IsHighlighted(doc: Doc) {
         if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate || doc.opacity === 0) return false;
-        return doc[HighlightSym] || Doc.GetProto(doc)[HighlightSym];
+        return doc[Highlight] || Doc.GetProto(doc)[Highlight];
     }
     export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true, presEffect?: Doc) {
         runInAction(() => {
             highlightedDocs.add(doc);
-            doc[HighlightSym] = true;
-            doc[AnimationSym] = presEffect;
+            doc[Highlight] = true;
+            doc[Animation] = presEffect;
             if (dataAndDisplayDocs) {
                 highlightedDocs.add(Doc.GetProto(doc));
-                Doc.GetProto(doc)[HighlightSym] = true;
+                Doc.GetProto(doc)[Highlight] = true;
             }
         });
     }
@@ -1367,8 +1361,8 @@ export namespace Doc {
             (doc ? [doc] : Array.from(highlightedDocs)).forEach(doc => {
                 highlightedDocs.delete(doc);
                 highlightedDocs.delete(Doc.GetProto(doc));
-                doc[HighlightSym] = Doc.GetProto(doc)[HighlightSym] = false;
-                doc[AnimationSym] = undefined;
+                doc[Highlight] = Doc.GetProto(doc)[Highlight] = false;
+                doc[Animation] = undefined;
             });
         });
     }
diff --git a/src/fields/DocSymbols.ts b/src/fields/DocSymbols.ts
new file mode 100644
index 000000000..65decc147
--- /dev/null
+++ b/src/fields/DocSymbols.ts
@@ -0,0 +1,26 @@
+export const Update = Symbol('DocUpdate');
+export const Self = Symbol('DocSelf');
+export const SelfProxy = Symbol('DocSelfProxy');
+export const FieldKeys = Symbol('DocFieldKeys');
+export const FieldTuples = Symbol('DocFieldTuples');
+export const Width = Symbol('DocWidth');
+export const Height = Symbol('DocHeight');
+export const Animation = Symbol('DocAnimation');
+export const Highlight = Symbol('DocHighlight');
+export const DocData = Symbol('DocData');
+export const DocLayout = Symbol('DocLayout');
+export const DocFields = Symbol('DocFields');
+export const DocCss = Symbol('DocCss');
+export const DocAcl = Symbol('DocAcl');
+export const DirectLinks = Symbol('DocDirectLinks');
+export const AclUnset = Symbol('DocAclUnset');
+export const AclPrivate = Symbol('DocAclOwnerOnly');
+export const AclReadonly = Symbol('DocAclReadOnly');
+export const AclAugment = Symbol('DocAclAugment');
+export const AclSelfEdit = Symbol('DocAclSelfEdit');
+export const AclEdit = Symbol('DocAclEdit');
+export const AclAdmin = Symbol('DocAclAdmin');
+export const UpdatingFromServer = Symbol('DocUpdatingFromServer');
+export const Initializing = Symbol('DocInitializing');
+export const ForceServerWrite = Symbol('DocForceServerWrite');
+export const CachedUpdates = Symbol('DocCachedUpdates');
diff --git a/src/fields/FieldSymbols.ts b/src/fields/FieldSymbols.ts
index e50c2856f..c381f14f5 100644
--- a/src/fields/FieldSymbols.ts
+++ b/src/fields/FieldSymbols.ts
@@ -1,12 +1,9 @@
-export const Update = Symbol('Update');
-export const Self = Symbol('Self');
-export const SelfProxy = Symbol('SelfProxy');
-export const HandleUpdate = Symbol('HandleUpdate');
-export const Id = Symbol('Id');
-export const OnUpdate = Symbol('OnUpdate');
-export const Parent = Symbol('Parent');
-export const Copy = Symbol('Copy');
-export const ToValue = Symbol('ToValue');
-export const ToScriptString = Symbol('ToScriptString');
-export const ToPlainText = Symbol('ToPlainText');
-export const ToString = Symbol('ToString');
+export const HandleUpdate = Symbol('FieldHandleUpdate');
+export const Id = Symbol('FieldId');
+export const OnUpdate = Symbol('FieldOnUpdate');
+export const Parent = Symbol('FieldParent');
+export const Copy = Symbol('FieldCopy');
+export const ToValue = Symbol('FieldToValue');
+export const ToScriptString = Symbol('FieldToScriptString');
+export const ToPlainText = Symbol('FieldToPlainText');
+export const ToString = Symbol('FieldToString');
diff --git a/src/fields/List.ts b/src/fields/List.ts
index dfd24cf7a..033fa569b 100644
--- a/src/fields/List.ts
+++ b/src/fields/List.ts
@@ -3,8 +3,9 @@ import { alias, list, serializable } from 'serializr';
 import { DocServer } from '../client/DocServer';
 import { ScriptingGlobals } from '../client/util/ScriptingGlobals';
 import { afterDocDeserialize, autoObject, Deserializable } from '../client/util/SerializationHelper';
+import { FieldTuples, Self, SelfProxy, Update } from './DocSymbols';
 import { Field } from './Doc';
-import { Copy, OnUpdate, Parent, Self, SelfProxy, ToScriptString, ToString, Update } from './FieldSymbols';
+import { Copy, OnUpdate, Parent, ToScriptString, ToString } from './FieldSymbols';
 import { ObjectField } from './ObjectField';
 import { ProxyField } from './Proxy';
 import { RefField } from './RefField';
@@ -21,12 +22,12 @@ const listHandlers: any = {
         if (value instanceof RefField) {
             throw new Error('fill with RefFields not supported yet');
         }
-        const res = this[Self].__fields.fill(value, start, end);
+        const res = this[Self].__fieldTuples.fill(value, start, end);
         this[Update]();
         return res;
     },
     pop(): any {
-        const field = toRealField(this[Self].__fields.pop());
+        const field = toRealField(this[Self].__fieldTuples.pop());
         this[Update]();
         return field;
     },
@@ -34,7 +35,7 @@ const listHandlers: any = {
         items = items.map(toObjectField);
 
         const list = this[Self];
-        const length = list.__fields.length;
+        const length = list.__fieldTuples.length;
         for (let i = 0; i < items.length; i++) {
             const item = items[i];
             //TODO Error checking to make sure parent doesn't already exist
@@ -43,23 +44,23 @@ const listHandlers: any = {
                 item[OnUpdate] = updateFunction(list, i + length, item, this);
             }
         }
-        const res = list.__fields.push(...items);
+        const res = list.__fieldTuples.push(...items);
         this[Update]({ op: '$addToSet', items, length: length + items.length });
         return res;
     }),
     reverse() {
-        const res = this[Self].__fields.reverse();
+        const res = this[Self].__fieldTuples.reverse();
         this[Update]();
         return res;
     },
     shift() {
-        const res = toRealField(this[Self].__fields.shift());
+        const res = toRealField(this[Self].__fieldTuples.shift());
         this[Update]();
         return res;
     },
     sort(cmpFunc: any) {
         this[Self].__realFields(); // coerce retrieving entire array
-        const res = this[Self].__fields.sort(cmpFunc ? (first: any, second: any) => cmpFunc(toRealField(first), toRealField(second)) : undefined);
+        const res = this[Self].__fieldTuples.sort(cmpFunc ? (first: any, second: any) => cmpFunc(toRealField(first), toRealField(second)) : undefined);
         this[Update]();
         return res;
     },
@@ -67,7 +68,7 @@ const listHandlers: any = {
         this[Self].__realFields(); // coerce retrieving entire array
         items = items.map(toObjectField);
         const list = this[Self];
-        const removed = list.__fields.filter((item: any, i: number) => i >= start && i < start + deleteCount);
+        const removed = list.__fieldTuples.filter((item: any, i: number) => i >= start && i < start + deleteCount);
         for (let i = 0; i < items.length; i++) {
             const item = items[i];
             //TODO Error checking to make sure parent doesn't already exist
@@ -77,18 +78,18 @@ const listHandlers: any = {
                 item[OnUpdate] = updateFunction(list, i + start, item, this);
             }
         }
-        let hintArray: {val : any, index : number}[] = [];
-        for(let i = start; i < start + deleteCount; i++) {
-            hintArray.push({val : list.__fields[i], index : i});
+        let hintArray: { val: any; index: number }[] = [];
+        for (let i = start; i < start + deleteCount; i++) {
+            hintArray.push({ val: list.__fieldTuples[i], index: i });
         }
-        const res = list.__fields.splice(start, deleteCount, ...items);
+        const res = list.__fieldTuples.splice(start, deleteCount, ...items);
         // the hint object sends the starting index of the slice and the number
-        // of elements to delete. 
+        // of elements to delete.
         this[Update](
             items.length === 0 && deleteCount
-                ? { op: '$remFromSet', items: removed, hint : { start : start, deleteCount : deleteCount }, length: list.__fields.length }
-                : items.length && !deleteCount && start === list.__fields.length
-                ? { op: '$addToSet', items, length: list.__fields.length }
+                ? { op: '$remFromSet', items: removed, hint: { start: start, deleteCount: deleteCount }, length: list.__fieldTuples.length }
+                : items.length && !deleteCount && start === list.__fieldTuples.length
+                ? { op: '$addToSet', items, length: list.__fieldTuples.length }
                 : undefined
         );
         return res.map(toRealField);
@@ -96,7 +97,7 @@ const listHandlers: any = {
     unshift(...items: any[]) {
         items = items.map(toObjectField);
         const list = this[Self];
-        const length = list.__fields.length;
+        const length = list.__fieldTuples.length;
         for (let i = 0; i < items.length; i++) {
             const item = items[i];
             //TODO Error checking to make sure parent doesn't already exist
@@ -106,32 +107,32 @@ const listHandlers: any = {
                 item[OnUpdate] = updateFunction(list, i, item, this);
             }
         }
-        const res = this[Self].__fields.unshift(...items);
+        const res = this[Self].__fieldTuples.unshift(...items);
         this[Update]();
         return res;
     },
     /// Accessor methods
     concat: action(function (this: any, ...items: any[]) {
         this[Self].__realFields();
-        return this[Self].__fields.map(toRealField).concat(...items);
+        return this[Self].__fieldTuples.map(toRealField).concat(...items);
     }),
     includes(valueToFind: any, fromIndex: number) {
         if (valueToFind instanceof RefField) {
             return this[Self].__realFields().includes(valueToFind, fromIndex);
         } else {
-            return this[Self].__fields.includes(valueToFind, fromIndex);
+            return this[Self].__fieldTuples.includes(valueToFind, fromIndex);
         }
     },
     indexOf(valueToFind: any, fromIndex: number) {
         if (valueToFind instanceof RefField) {
             return this[Self].__realFields().indexOf(valueToFind, fromIndex);
         } else {
-            return this[Self].__fields.indexOf(valueToFind, fromIndex);
+            return this[Self].__fieldTuples.indexOf(valueToFind, fromIndex);
         }
     },
     join(separator: any) {
         this[Self].__realFields();
-        return this[Self].__fields.map(toRealField).join(separator);
+        return this[Self].__fieldTuples.map(toRealField).join(separator);
     },
     lastElement() {
         return this[Self].__realFields().lastElement();
@@ -140,12 +141,12 @@ const listHandlers: any = {
         if (valueToFind instanceof RefField) {
             return this[Self].__realFields().lastIndexOf(valueToFind, fromIndex);
         } else {
-            return this[Self].__fields.lastIndexOf(valueToFind, fromIndex);
+            return this[Self].__fieldTuples.lastIndexOf(valueToFind, fromIndex);
         }
     },
     slice(begin: number, end: number) {
         this[Self].__realFields();
-        return this[Self].__fields.slice(begin, end).map(toRealField);
+        return this[Self].__fieldTuples.slice(begin, end).map(toRealField);
     },
 
     /// Iteration methods
@@ -156,55 +157,55 @@ const listHandlers: any = {
         return this[Self].__realFields().every(callback, thisArg);
         // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
         // If we don't want to support the array parameter, we should use this version instead
-        // return this[Self].__fields.every((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+        // return this[Self].__fieldTuples.every((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
     },
     filter(callback: any, thisArg: any) {
         return this[Self].__realFields().filter(callback, thisArg);
         // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
         // If we don't want to support the array parameter, we should use this version instead
-        // return this[Self].__fields.filter((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+        // return this[Self].__fieldTuples.filter((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
     },
     find(callback: any, thisArg: any) {
         return this[Self].__realFields().find(callback, thisArg);
         // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
         // If we don't want to support the array parameter, we should use this version instead
-        // return this[Self].__fields.find((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+        // return this[Self].__fieldTuples.find((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
     },
     findIndex(callback: any, thisArg: any) {
         return this[Self].__realFields().findIndex(callback, thisArg);
         // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
         // If we don't want to support the array parameter, we should use this version instead
-        // return this[Self].__fields.findIndex((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+        // return this[Self].__fieldTuples.findIndex((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
     },
     forEach(callback: any, thisArg: any) {
         return this[Self].__realFields().forEach(callback, thisArg);
         // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
         // If we don't want to support the array parameter, we should use this version instead
-        // return this[Self].__fields.forEach((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+        // return this[Self].__fieldTuples.forEach((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
     },
     map(callback: any, thisArg: any) {
         return this[Self].__realFields().map(callback, thisArg);
         // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
         // If we don't want to support the array parameter, we should use this version instead
-        // return this[Self].__fields.map((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+        // return this[Self].__fieldTuples.map((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
     },
     reduce(callback: any, initialValue: any) {
         return this[Self].__realFields().reduce(callback, initialValue);
         // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
         // If we don't want to support the array parameter, we should use this version instead
-        // return this[Self].__fields.reduce((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue);
+        // return this[Self].__fieldTuples.reduce((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue);
     },
     reduceRight(callback: any, initialValue: any) {
         return this[Self].__realFields().reduceRight(callback, initialValue);
         // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
         // If we don't want to support the array parameter, we should use this version instead
-        // return this[Self].__fields.reduceRight((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue);
+        // return this[Self].__fieldTuples.reduceRight((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue);
     },
     some(callback: any, thisArg: any) {
         return this[Self].__realFields().some(callback, thisArg);
         // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
         // If we don't want to support the array parameter, we should use this version instead
-        // return this[Self].__fields.some((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+        // return this[Self].__fieldTuples.some((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
     },
     values() {
         return this[Self].__realFields().values();
@@ -246,7 +247,7 @@ type ListUpdate<T> = ListSpliceUpdate<T> | ListIndexUpdate<T>;
 
 type StoredType<T extends Field> = T extends RefField ? ProxyField<T> : T;
 
-export const ListFieldName="fields";
+export const ListFieldName = 'fields';
 @Deserializable('list')
 class ListImpl<T extends Field> extends ObjectField {
     constructor(fields?: T[]) {
@@ -254,9 +255,9 @@ class ListImpl<T extends Field> extends ObjectField {
         const list = new Proxy<this>(this, {
             set: setter,
             get: listGetter,
-            ownKeys: target => Object.keys(target.__fields),
+            ownKeys: target => Object.keys(target.__fieldTuples),
             getOwnPropertyDescriptor: (target, prop) => {
-                if (prop in target.__fields) {
+                if (prop in target[FieldTuples]) {
                     return {
                         configurable: true, //TODO Should configurable be true?
                         enumerable: true,
@@ -281,7 +282,7 @@ class ListImpl<T extends Field> extends ObjectField {
     // this requests all ProxyFields at the same time to avoid the overhead
     // of separate network requests and separate updates to the React dom.
     private __realFields() {
-        const unrequested = this.__fields.filter(f => f instanceof ProxyField && f.needsRequesting).map(f => f as ProxyField<RefField>);
+        const unrequested = this[FieldTuples].filter(f => f instanceof ProxyField && f.needsRequesting).map(f => f as ProxyField<RefField>);
         // if we find any ProxyFields that don't have a current value, then
         // start the server request for all of them
         if (unrequested.length) {
@@ -293,17 +294,16 @@ class ListImpl<T extends Field> extends ObjectField {
             // will await the batch request and return the requested field value.
             unrequested.forEach(p => p.setExternalValuePromise(allSetPromise));
         }
-        return this.__fields.map(toRealField);
+        return this[FieldTuples].map(toRealField);
     }
 
-    public static FieldDataName = 'fields';
     @serializable(alias(ListFieldName, list(autoObject(), { afterDeserialize: afterDocDeserialize })))
-    private get __fields() {
-        return this.___fields;
+    private get __fieldTuples() {
+        return this[FieldTuples];
     }
 
-    private set __fields(value) {
-        this.___fields = value;
+    private set __fieldTuples(value) {
+        this[FieldTuples] = value;
         for (const key in value) {
             const field = value[key];
             if (field instanceof ObjectField) {
@@ -314,14 +314,14 @@ class ListImpl<T extends Field> extends ObjectField {
     }
 
     [Copy]() {
-        const copiedData = this[Self].__fields.map(f => (f instanceof ObjectField ? f[Copy]() : f));
+        const copiedData = this[Self].__fieldTuples.map(f => (f instanceof ObjectField ? f[Copy]() : f));
         const deepCopy = new ListImpl<T>(copiedData as any);
         return deepCopy;
     }
 
     // @serializable(alias("fields", list(autoObject())))
     @observable
-    private ___fields: StoredType<T>[] = [];
+    private [FieldTuples]: StoredType<T>[] = [];
 
     private [Update] = (diff: any) => {
         // console.log(diff);
diff --git a/src/fields/util.ts b/src/fields/util.ts
index a2b445d6c..62f27c7d2 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -7,28 +7,9 @@ import { SerializationHelper } from '../client/util/SerializationHelper';
 import { UndoManager } from '../client/util/UndoManager';
 import { returnZero } from '../Utils';
 import CursorField from './CursorField';
-import {
-    AclAdmin,
-    AclEdit,
-    aclLevel,
-    AclPrivate,
-    AclSelfEdit,
-    AclSym,
-    DataSym,
-    Doc,
-    DocListCast,
-    DocListCastAsync,
-    ForceServerWrite,
-    HeightSym,
-    HierarchyMapping,
-    Initializing,
-    LayoutSym,
-    ReverseHierarchyMap,
-    updateCachedAcls,
-    UpdatingFromServer,
-    WidthSym,
-} from './Doc';
-import { Id, OnUpdate, Parent, SelfProxy, ToValue, Update } from './FieldSymbols';
+import { aclLevel, Doc, DocListCast, DocListCastAsync, HierarchyMapping, ReverseHierarchyMap, updateCachedAcls } from './Doc';
+import { AclAdmin, AclEdit, AclPrivate, AclSelfEdit, DocAcl, DocData, DocLayout, FieldKeys, ForceServerWrite, Height, Initializing, SelfProxy, Update, UpdatingFromServer, Width } from './DocSymbols';
+import { Id, OnUpdate, Parent, ToValue } from './FieldSymbols';
 import { List } from './List';
 import { ObjectField } from './ObjectField';
 import { PrefetchProxy, ProxyField } from './Proxy';
@@ -56,7 +37,7 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
     if (value !== undefined) {
         value = value[SelfProxy] || value;
     }
-    const curValue = target.__fields[prop];
+    const curValue = target.__fieldTuples[prop];
     if (curValue === value || (curValue instanceof ProxyField && value instanceof RefField && curValue.fieldId === value[Id])) {
         // TODO This kind of checks correctly in the case that curValue is a ProxyField and value is a RefField, but technically
         // curValue should get filled in with value if it isn't already filled in, in case we fetched the referenced field some other way
@@ -88,11 +69,11 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
 
     if (writeToDoc) {
         if (value === undefined) {
-            target.__fieldKeys && delete target.__fieldKeys[prop];
-            delete target.__fields[prop];
+            delete target[FieldKeys][prop];
+            delete target.__fieldTuples[prop];
         } else {
-            target.__fieldKeys && (target.__fieldKeys[prop] = true);
-            target.__fields[prop] = value;
+            target[FieldKeys][prop] = true;
+            target.__fieldTuples[prop] = value;
         }
 
         if (writeToServer) {
@@ -148,12 +129,12 @@ export function denormalizeEmail(email: string) {
  */
 export function inheritParentAcls(parent: Doc, child: Doc) {
     return;
-    const dataDoc = parent[DataSym];
-    for (const key of Object.keys(dataDoc)) {
-        // if the default acl mode is private, then don't inherit the acl-Public permission, but set it to private.
-        const permission = key === 'acl-Public' && Doc.defaultAclPrivate ? AclPrivate : dataDoc[key];
-        key.startsWith('acl') && distributeAcls(key, permission, child);
-    }
+    // const dataDoc = parent[DataSym];
+    // for (const key of Object.keys(dataDoc)) {
+    //     // if the default acl mode is private, then don't inherit the acl-Public permission, but set it to private.
+    //     const permission = key === 'acl-Public' && Doc.defaultAclPrivate ? AclPrivate : dataDoc[key];
+    //     key.startsWith('acl') && distributeAcls(key, permission, child);
+    // }
 }
 
 /**
@@ -212,7 +193,7 @@ export function SetCachedGroups(groups: string[]) {
     runInAction(() => cachedGroups.push(...groups));
 }
 function getEffectiveAcl(target: any, user?: string): symbol {
-    const targetAcls = target[AclSym];
+    const targetAcls = target[DocAcl];
     if (targetAcls?.['acl-Me'] === AclAdmin || GetCachedGroupByName('Admin')) return AclAdmin;
 
     const userChecked = user || Doc.CurrentUserEmail; // if the current user is the author of the document / the current user is a member of the admin group
@@ -237,7 +218,7 @@ function getEffectiveAcl(target: any, user?: string): symbol {
         return DocServer?.Control?.isReadOnly?.() && HierarchyMapping.get(effectiveAcl)!.level < aclLevel.editable ? AclEdit : effectiveAcl;
     }
     // authored documents are private until an ACL is set.
-    const targetAuthor = target.__fields?.author || target.author; // target may be a Doc of Proxy, so check __fields.author and .author
+    const targetAuthor = target.__fieldTuples?.author || target.author; // target may be a Doc of Proxy, so check __fieldTuples.author and .author
     if (targetAuthor && targetAuthor !== userChecked) return AclPrivate;
     return AclAdmin;
 }
@@ -277,7 +258,7 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
     }
 
     let dataDocChanged = false;
-    const dataDoc = target[DataSym];
+    const dataDoc = target[DocData];
     if (dataDoc && (!inheritingFromCollection || !dataDoc[key] || ReverseHierarchyMap.get(StrCast(dataDoc[key]))! > ReverseHierarchyMap.get(acl)!)) {
         if (GetEffectiveAcl(dataDoc) === AclAdmin) {
             dataDoc[key] = acl;
@@ -290,13 +271,13 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
         // maps over the children of the document
         DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc)]).forEach(d => {
             distributeAcls(key, acl, d, inheritingFromCollection, visited);
-            distributeAcls(key, acl, d[DataSym], inheritingFromCollection, visited);
+            distributeAcls(key, acl, d[DocData], inheritingFromCollection, visited);
         });
 
         // maps over the annotations of the document
         DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + '_annotations']).forEach(d => {
             distributeAcls(key, acl, d, inheritingFromCollection, visited);
-            distributeAcls(key, acl, d[DataSym], inheritingFromCollection, visited);
+            distributeAcls(key, acl, d[DocData], inheritingFromCollection, visited);
         });
     }
 
@@ -311,16 +292,16 @@ export function setter(target: any, in_prop: string | symbol | number, value: an
     // if you're trying to change an acl but don't have Admin access / you're trying to change it to something that isn't an acceptable acl, you can't
     if (typeof prop === 'string' && prop.startsWith('acl') && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined].includes(value))) return true;
 
-    if (typeof prop === 'string' && prop !== '__id' && prop !== '__fields' && prop.startsWith('_')) {
+    if (typeof prop === 'string' && prop !== '__id' && prop !== '__fieldTuples' && prop.startsWith('_')) {
         if (!prop.startsWith('__')) prop = prop.substring(1);
         if (target.__LAYOUT__) {
             target.__LAYOUT__[prop] = value;
             return true;
         }
     }
-    if (target.__fields[prop] instanceof ComputedField) {
-        if (target.__fields[prop].setterscript && value !== undefined && !(value instanceof ComputedField)) {
-            return ScriptCast(target.__fields[prop])?.setterscript?.run({ self: target[SelfProxy], this: target[SelfProxy], value }).success ? true : false;
+    if (target.__fieldTuples[prop] instanceof ComputedField) {
+        if (target.__fieldTuples[prop].setterscript && value !== undefined && !(value instanceof ComputedField)) {
+            return ScriptCast(target.__fieldTuples[prop])?.setterscript?.run({ self: target[SelfProxy], this: target[SelfProxy], value }).success ? true : false;
         }
     }
     return _setter(target, prop, value, receiver);
@@ -330,14 +311,13 @@ export function getter(target: any, prop: string | symbol, proxy: any): any {
     // prettier-ignore
     switch (prop) {
         case 'then' :   return undefined;
-        case '__fields' :   case '__id':  
-        case 'constructor': case 'toString': case 'valueOf':  
-        case 'factory':     case 'serializeInfo': 
+        case 'constructor':   case 'toString': case 'valueOf':  
+        case 'serializeInfo': case 'factory':     
                         return target[prop];
-        case AclSym :   return target[AclSym];
-        case $mobx:     return target.__fields[prop];
-        case LayoutSym: return target.__LAYOUT__;
-        case HeightSym: case WidthSym: if (GetEffectiveAcl(target) === AclPrivate) return returnZero;
+        case DocAcl :   return target[DocAcl];
+        case $mobx:     return target.__fieldTuples[prop];
+        case DocLayout: return target.__LAYOUT__;
+        case Height: case Width: if (GetEffectiveAcl(target) === AclPrivate) return returnZero;
         default :
             if (typeof prop === 'symbol')  return target[prop];
             if (prop.startsWith('isMobX')) return target[prop];
@@ -351,7 +331,7 @@ export function getter(target: any, prop: string | symbol, proxy: any): any {
 }
 
 function getFieldImpl(target: any, prop: string | number, proxy: any, ignoreProto: boolean = false): any {
-    const field = target.__fields[prop];
+    const field = target.__fieldTuples[prop];
     const value = field?.[ToValue]?.(proxy); // converts ComputedFields to values, or unpacks ProxyFields into Proxys
     if (value) return value.value;
     if (field === undefined && !ignoreProto && prop !== 'proto') {
-- 
cgit v1.2.3-70-g09d2


From bf16eca7a84adfdf1c5970e7e4793568ee70325d Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 13 Jun 2023 22:14:29 -0400
Subject: fixed updating cached docs when opening a backlinks collection. 
 added some FieldInfo types and added 'enumeration' field display in schema
 view.  fixed bug in schema view column sizing.   updated a bunch of standard
 field names to be more consistent.

---
 src/client/documents/Documents.ts                  | 471 +++++++++++----------
 src/client/util/CurrentUserUtils.ts                | 115 ++---
 src/client/util/DocumentManager.ts                 |   2 +-
 src/client/util/DragManager.ts                     |   2 +-
 src/client/util/LinkManager.ts                     |   2 +-
 src/client/util/TrackMovements.ts                  |   3 +-
 src/client/views/DashboardView.tsx                 |   9 +-
 src/client/views/DocComponent.tsx                  |   2 -
 src/client/views/DocumentButtonBar.tsx             |   2 +-
 src/client/views/DocumentDecorations.tsx           |  16 +-
 src/client/views/FilterPanel.tsx                   |  12 +-
 src/client/views/GestureOverlay.tsx                |   8 +-
 src/client/views/LightboxView.tsx                  |  12 +-
 src/client/views/MainView.tsx                      |  20 +-
 src/client/views/MarqueeAnnotator.tsx              |   2 +-
 src/client/views/OverlayView.tsx                   |   4 +-
 src/client/views/Palette.tsx                       |   4 +-
 src/client/views/PropertiesButtons.tsx             |   4 +-
 .../views/PropertiesDocBacklinksSelector.tsx       |   4 +-
 src/client/views/PropertiesView.tsx                |  44 +-
 src/client/views/SidebarAnnos.tsx                  |  12 +-
 src/client/views/StyleProvider.tsx                 |  23 +-
 src/client/views/TemplateMenu.tsx                  |   4 +-
 src/client/views/collections/CollectionMenu.tsx    |  10 +-
 .../views/collections/CollectionNoteTakingView.tsx | 104 ++---
 .../views/collections/CollectionPileView.tsx       |  21 +-
 .../collections/CollectionStackedTimeline.tsx      |  14 +-
 .../views/collections/CollectionStackingView.tsx   |  12 +-
 src/client/views/collections/CollectionSubView.tsx |  29 +-
 .../views/collections/CollectionTimeView.tsx       |  18 +-
 .../views/collections/CollectionTreeView.tsx       |  12 +-
 src/client/views/collections/TabDocView.tsx        |   8 +-
 src/client/views/collections/TreeView.tsx          |  24 +-
 .../CollectionFreeFormLinkView.tsx                 |  16 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  14 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |   2 +-
 .../collectionLinear/CollectionLinearView.tsx      |  18 +-
 .../CollectionMulticolumnView.tsx                  |   4 +-
 .../CollectionMultirowView.tsx                     |   4 +-
 .../collectionSchema/CollectionSchemaView.scss     |   2 +-
 .../collectionSchema/CollectionSchemaView.tsx      |  56 +--
 .../collectionSchema/SchemaColumnHeader.tsx        |   3 +-
 .../collections/collectionSchema/SchemaRowBox.tsx  |  10 +-
 .../collectionSchema/SchemaTableCell.tsx           |  55 ++-
 src/client/views/linking/LinkMenuGroup.tsx         |   2 +-
 src/client/views/linking/LinkPopup.tsx             |   4 +-
 .../views/nodes/DataVizBox/components/TableBox.tsx |   2 +-
 src/client/views/nodes/DocumentLinksButton.tsx     |   6 +-
 src/client/views/nodes/DocumentView.tsx            |  43 +-
 src/client/views/nodes/KeyValuePair.tsx            |   4 +-
 src/client/views/nodes/LabelBox.tsx                |   4 +-
 src/client/views/nodes/LinkAnchorBox.tsx           |   4 +-
 src/client/views/nodes/LinkDocPreview.tsx          |   4 +-
 src/client/views/nodes/MapBox/MapBox.tsx           |   8 +-
 src/client/views/nodes/MapBox/MapBox2.tsx          |   8 +-
 src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx |   2 +-
 src/client/views/nodes/PDFBox.tsx                  |   4 +-
 src/client/views/nodes/QueryBox.scss               |   5 -
 src/client/views/nodes/QueryBox.tsx                |  38 --
 src/client/views/nodes/VideoBox.tsx                |   4 +-
 src/client/views/nodes/WebBox.tsx                  |   8 +-
 src/client/views/nodes/button/FontIconBox.tsx      |   2 +-
 .../views/nodes/formattedText/DashDocView.tsx      |   4 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  22 +-
 src/client/views/nodes/trails/PresBox.tsx          |  14 +-
 src/client/views/nodes/trails/PresElementBox.tsx   |  10 +-
 src/client/views/pdf/PDFViewer.tsx                 |  10 +-
 src/fields/Doc.ts                                  |  44 +-
 src/fields/documentSchemas.ts                      |  10 +-
 src/fields/util.ts                                 |   8 +-
 src/mobile/AudioUpload.tsx                         |  10 +-
 src/mobile/MobileInterface.tsx                     |   4 +-
 72 files changed, 740 insertions(+), 761 deletions(-)
 delete mode 100644 src/client/views/nodes/QueryBox.scss
 delete mode 100644 src/client/views/nodes/QueryBox.tsx

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index c1abd6e71..a91d5806c 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -110,18 +110,28 @@ class DocInfo extends FInfo {
     }
 }
 class DimInfo extends FInfo {
-    fieldType? = 'DimUnit';
+    fieldType? = 'enumeration';
     values? = [DimUnit.Pixel, DimUnit.Ratio];
-    readOnly = true;
+    readOnly = false;
 }
 class PEInfo extends FInfo {
-    fieldType? = 'pointerEvents';
+    fieldType? = 'enumeration';
     values? = ['all', 'none'];
-    readOnly = true;
+    readOnly = false;
 }
 class DAInfo extends FInfo {
-    fieldType? = 'dropActionType';
+    fieldType? = 'enumeration';
     values? = ['embed', 'copy', 'move', 'same', 'proto', 'none'];
+    readOnly = false;
+}
+class CTypeInfo extends FInfo {
+    fieldType? = 'enumeration';
+    values? = Array.from(Object.keys(CollectionViewType));
+    readOnly = false;
+}
+class DTypeInfo extends FInfo {
+    fieldType? = 'enumeration';
+    values? = Array.from(Object.keys(DocumentType));
     readOnly = true;
 }
 class DateInfo extends FInfo {
@@ -134,272 +144,267 @@ type STRt = StrInfo | string;
 type DOCt = DocInfo | Doc;
 type DIMt = DimInfo | typeof DimUnit.Pixel | typeof DimUnit.Ratio;
 type PEVt = PEInfo | 'none' | 'all';
+type COLLt = CTypeInfo | CollectionViewType;
 type DROPt = DAInfo | dropActionType;
 type DATEt = DateInfo | number;
+type DTYPEt = DTypeInfo | string;
 export class DocumentOptions {
+    // coordinate and dimensions depending on view
     x?: NUMt = new NumInfo('x coordinate of document in a freeform view');
     y?: NUMt = new NumInfo('y coordinage of document in a freeform view');
     z?: NUMt = new NumInfo('whether document is in overlay (1) or not (0)', false, [1, 0]);
-    isSystem?: BOOLt = new BoolInfo('is this a system created/owned doc', true);
-    type?: STRt = new StrInfo('type of document', true, Array.from(Object.keys(DocumentType)));
-    title?: string;
-    author_date?: DATEt = new DateInfo('date the document was created', true);
-    _dropAction?: DROPt = new DAInfo("what should happen to this document when it's dropped somewhere else");
-    allowOverlayDrop?: BOOLt = new BoolInfo('can documents be dropped onto this document without using dragging title bar or holding down embed key (ctrl)?', true);
-    childDropAction?: DROPt = new DAInfo("what should happen to the source document when it's dropped onto a child of a collection ");
-    targetDropAction?: DROPt = new DAInfo('what should happen to the source document when ??? ');
-    userColor?: STRt = new StrInfo('color associated with a Dash user (seen in header fields of shared documents)');
-    color?: STRt = new StrInfo('foreground color data doc');
-    backgroundColor?: STRt = new StrInfo('background color for data doc');
-    _layout_autoHeight?: BOOLt = new BoolInfo('whether document automatically resizes vertically to display contents');
-    _headerHeight?: NUMt = new NumInfo('height of document header used for displaying title');
-    _headerFontSize?: NUMt = new NumInfo('font size of header of custom notes');
-    _headerPointerEvents?: PEVt = new PEInfo('types of events the header of a custom text document can consume');
-    _freeform_panX?: NUMt = new NumInfo('horizontal pan location of a freeform view');
-    _freeform_panY?: NUMt = new NumInfo('vertical pan location of a freeform view');
+    _dimMagnitude?: NUMt = new NumInfo("magnitude of collectionMulti{row,col} element's width or height");
+    _dimUnit?: DIMt = new DimInfo("units of collectionMulti{row,col} element's width or height - 'px' or '*' for pixels or relative units");
+    lat?: NUMt = new NumInfo('latitude coordinate for map views');
+    lng?: NUMt = new NumInfo('longitude coordinate for map views');
+    _timecodeToShow?: NUMt = new NumInfo('the time that a document should be displayed (e.g., when an annotation shows up as a video plays)');
+    _timecodeToHide?: NUMt = new NumInfo('the time that a document should be hidden');
     _width?: NUMt = new NumInfo('displayed width of a document');
     _height?: NUMt = new NumInfo('displayed height of document');
     data_nativeWidth?: NUMt = new NumInfo('native width of data field contents (e.g., the pixel width of an image)');
     data_nativeHeight?: NUMt = new NumInfo('native height of data field contents (e.g., the pixel height of an image)');
+    linearBtnWidth?: NUMt = new NumInfo('unexpanded width of a linear menu button (button "width" changes when it expands)');
     _nativeWidth?: NUMt = new NumInfo('native width of document contents (e.g., the pixel width of an image)');
     _nativeHeight?: NUMt = new NumInfo('native height of document contents (e.g., the pixel height of an image)');
     _nativeDimModifiable?: BOOLt = new BoolInfo('native dimensions can be modified using document decoration reizers');
     _nativeHeightUnfrozen?: BOOLt = new BoolInfo('native height can be changed independent of width by dragging decoration resizers');
-    _dimMagnitude?: NUMt = new NumInfo("magnitude of collectionMulti{row,col} element's width or height");
-    _dimUnit?: DIMt = new DimInfo("units of collectionMulti{row,col} element's width or height - 'px' or '*' for pixels or relative units");
+
+    'acl-Public'?: string; // public permissions
+    '_acl-Public'?: string; // public permissions
+    type?: DTYPEt = new DTypeInfo('type of document', true);
+    _type_collection?: COLLt = new CTypeInfo('how collection is rendered'); // sub type of a collection
+    title?: STRt = new StrInfo('title of document');
+    caption?: RichTextField;
+    author?: string; // STRt = new StrInfo('creator of document'); // bcz: don't change this. Otherwise, the userDoc's field Infos will have a FieldInfo assigned to its author field which will render it unreadable
+    author_date?: DATEt = new DateInfo('date the document was created', true);
+    annotationOn?: DOCt = new DocInfo('document annotated by this document');
+    color?: STRt = new StrInfo('foreground color data doc');
+    hidden?: BOOLt = new BoolInfo('whether the document is not rendered by its collection');
+    backgroundColor?: STRt = new StrInfo('background color for data doc');
+    opacity?: NUMt = new NumInfo('document opacity');
+    viewTransitionTime?: NUMt = new NumInfo('transition duration for view parameters');
+    dontRegisterView?: BOOLt = new BoolInfo('are views of this document registered so that they can be found when following links, etc');
+    dontUndo?: BOOLt = new BoolInfo('whether button clicks should be undoable (true for Undo/Redo/and sidebar) AND whether modifications to document are undoable (true for linearview menu buttons to prevent open/close from entering undo stack)');
+    _headerHeight?: NUMt = new NumInfo('height of document header used for displaying title');
+    _headerFontSize?: NUMt = new NumInfo('font size of header of custom notes');
+    _headerPointerEvents?: PEVt = new PEInfo('types of events the header of a custom text document can consume');
+    _lockedPosition?: BOOLt = new BoolInfo("lock the x,y coordinates of the document so that it can't be dragged");
+    _lockedTransform?: BOOLt = new BoolInfo('lock the freeform_panx,freeform_pany and scale parameters of the document so that it be panned/zoomed');
+
+    layout?: string | Doc; // default layout string or template document
+    layout_keyValue?: STRt = new StrInfo('layout definition for showing keyValue view of document');
+    layout_explainer?: STRt = new StrInfo('explanation displayed at top of a collection to describe its purpose');
+    layout_headerButton?: DOCt = new DocInfo('the (button) Doc to display at the top of a collection.');
+    layout_disableBrushing?: BOOLt = new BoolInfo('whether to suppress border highlighting');
+    layout_unrendered?: BOOLt = new BoolInfo('denotes an annotation that is not rendered with a DocumentView (e.g, rtf/pdf text selections and links to scroll locations in web/pdf)');
+    layout_hideOpenButton?: BOOLt = new BoolInfo('whether to hide the open full screen button when selected');
+    layout_hideDocumentButtonBar?: BOOLt = new BoolInfo('whether to hide the document decorations lower button bar when selected');
+    layout_hideLinkAnchors?: BOOLt = new BoolInfo('suppresses link anchor dots from being displayed');
+    layout_hideAllLinks?: BOOLt = new BoolInfo('whether all individual blue anchor dots should be hidden');
+    layout_hideResizeHandles?: BOOLt = new BoolInfo('whether to hide the resize handles when selected');
+    layout_hideLinkButton?: BOOLt = new BoolInfo('whether the blue link counter button should be hidden');
+    layout_hideDecorationTitle?: BOOLt = new BoolInfo('whether to suppress the document decortations title when selected');
+    layout_borderRounding?: string;
+    layout_boxShadow?: string; // box-shadow css string OR "standard" to use dash standard box shadow
+    layout_maxAutoHeight?: NUMt = new NumInfo('maximum height for newly created (eg, from pasting) text documents');
+    _layout_autoHeight?: BOOLt = new BoolInfo('whether document automatically resizes vertically to display contents');
+    _layout_curPage?: NUMt = new NumInfo('current page of a PDF or other? paginated document');
+    _layout_currentTimecode?: NUMt = new NumInfo('the current timecode of a time-based document (e.g., current time of a video)  value is in seconds');
+    _layout_hideContextMenu?: BOOLt = new BoolInfo('whether the context menu can be shown');
     _layout_fitWidth?: BOOLt = new BoolInfo('whether document should scale its contents to fit its rendered width or not (e.g., for PDFviews)');
-    _layoutFitContentsToBox?: BOOLt = new BoolInfo('whether a freeformview should zoom/scale to create a shrinkwrapped view of its content');
-    _contentBounds?: List<number>; // the (forced) bounds of the document to display. format is: [left, top, right, bottom]
-    _lockedPosition?: boolean; // lock the x,y coordinates of the document so that it can't be dragged
-    _lockedTransform?: boolean; // lock the freeform_panx,freeform_pany and scale parameters of the document so that it be panned/zoomed
-    _followLinkToggle?: boolean; // whether document, when clicked, toggles display of its link target
+    _layout_fitContentsToBox?: BOOLt = new BoolInfo('whether a freeformview should zoom/scale to create a shrinkwrapped view of its content');
+    _layout_fieldKey?: STRt = new StrInfo('the field key containing the current layout definition');
+    _layout_enableAltContentUI?: BOOLt = new BoolInfo('whether to show alternate content button');
     _layout_showTitle?: string; // field name to display in header (:hover is an optional suffix)
-    _layout_altContentUI?: boolean; // whether to show alternate content button
-    _isLightbox?: boolean; // whether a collection acts as a lightbox by opening lightbox links by hiding all other documents in collection besides link target
+    _layout_showSidebar?: BOOLt = new BoolInfo('whether an annotationsidebar should be displayed for text docuemnts');
     _layout_showCaption?: string; // which field to display in the caption area.  leave empty to have no caption
-    _layoutScrollTop?: number; // scroll location for pdfs
-    _noAutoscroll?: boolean; // whether collections autoscroll when this item is dragged
-    _chromeHidden?: boolean; // whether the editing chrome for a document is hidden
-    _searchDoc?: boolean; // is this a search document (used to change UI for search results in schema view)
-    _forceActive?: boolean; // flag to handle pointer events when not selected (or otherwise active)
-    enableDragWhenActive?: boolean; // allow dragging even if document contentts are active (e.g., tree, groups)
-    _stayInCollection?: boolean; // whether the document should remain in its collection when someone tries to drag and drop it elsewhere
-    _raiseWhenDragged?: boolean; // whether a document is brought to front when dragged.
-    _hideContextMenu?: boolean; // whether the context menu can be shown
-    _type_collection?: string; // sub type of a collection
-    type_collection?: string; // sub type of a collection
-    _gridGap?: number; // gap between items in masonry view
-    freeform?: string; // placeholder just so that the key value pane has a divider to render for freeform view data
-    _freeform_scale?: number; // how much a freeform view has been scaled (zoomed)
-    _overflow?: string; // set overflow behavior
-    _xMargin?: number; // gap between left edge of document and start of masonry/stacking layouts
-    _yMargin?: number; // gap between top edge of dcoument and start of masonry/stacking layouts
-    _xPadding?: number;
-    _yPadding?: number;
-    _carousel_index?: number; // which item index the carousel viewer is showing
-    _layout_showSidebar?: boolean; //whether an annotationsidebar should be displayed for text docuemnts
+
+    _chromeHidden?: BOOLt = new BoolInfo('whether the editing chrome for a document is hidden');
+    _gridGap?: NUMt = new NumInfo('gap between items in masonry view');
+    _xMargin?: NUMt = new NumInfo('gap between left edge of document and start of masonry/stacking layouts');
+    _yMargin?: NUMt = new NumInfo('gap between top edge of dcoument and start of masonry/stacking layouts');
+    _xPadding?: NUMt = new NumInfo('x padding');
+    _yPadding?: NUMt = new NumInfo('y padding');
     _singleLine?: boolean; // whether label box is restricted to one line of text
     _createDocOnCR?: boolean; // whether carriage returns and tabs create new text documents
-    _minFontSize?: number; // minimum font size for labelBoxes
-    _maxFontSize?: number; // maximum font size for labelBoxes
-    _columnWidth?: number;
-    _columnsHideIfEmpty?: boolean; // whether stacking view column headings should be hidden
-    _text_fontSize?: string;
-    _text_fontFamily?: string;
-    _text_fontWeight?: string;
-    _pivotField?: string; // field key used to determine headings for sections in stacking, masonry, pivot views
-    _layout_curPage?: number; // current page of a PDF or other? paginated document
-    _layout_currentTimecode?: number; // the current timecode of a time-based document (e.g., current time of a video)  value is in seconds
-    _currentFrame?: number; // the current frame of a frame-based collection (e.g., progressive slide)
-    _timecodeToShow?: number; // the time that a document should be displayed (e.g., when an annotation shows up as a video plays)
-    _timecodeToHide?: number; // the time that a document should be hidden
-    _timelineLabel?: boolean; // whether the document exists on a timeline
+    _columnWidth?: NUMt = new NumInfo('width of table column');
+    _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', true);
     _caption_yMargin?: NUMt = new NumInfo('y margin of caption inside of a carousel collection', true);
     icon_nativeWidth?: NUMt = new NumInfo('native width of icon view', true);
     icon_nativeHeight?: NUMt = new NumInfo('native height of icon view', true);
-    dragFactory_count?: NUMt = new NumInfo('number of items created from a drag button (used for setting title with incrementing index)', true);
-    openFactoryLocation?: string; // an OpenWhere value to place the factory created document
-    openFactoryAsDelegate?: boolean; //
-    lat?: number;
-    lng?: number;
-    infoWindowOpen?: boolean;
-    author?: string;
-    _layout_fieldKey?: string;
-    fieldValues?: List<any>; // possible field values used by fieldInfos
-    fieldType?: string; // type of afield used by fieldInfos
-    layout_unrendered?: boolean; // denotes an annotation that is not rendered with a DocumentView (e.g, rtf/pdf text selections and links to scroll locations in web/pdf)
-    'acl-Public'?: string; // public permissions
-    '_acl-Public'?: string; // public permissions
-    version?: string; // version identifier for a document
-    label?: string;
-    hidden?: boolean;
-    _hidden?: boolean;
-    pointerEvents?: string; // pointer events that the documentview should have
-    mediaState?: string; // status of audio/video media document: "pendingRecording", "recording", "paused", "playing"
-    recording?: boolean; // whether WebCam is recording or not
-    autoPlayAnchors?: boolean; // whether to play audio/video when an anchor is clicked in a stackedTimeline.
-    dontPlayLinkOnSelect?: boolean; // whether an audio/video should start playing when a link is followed to it.
-    linkSource?: Doc; // the source document for a collection of backlinks
+    _text_fontSize?: string;
+    _text_fontFamily?: string;
+    _text_fontWeight?: string;
+    _pivotField?: string; // field key used to determine headings for sections in stacking, masonry, pivot views
+
+    infoWindowOpen?: BOOLt = new BoolInfo('whether info window corresponding to pin is open (on MapDocuments)');
+    _carousel_index?: NUMt = new NumInfo('which item index the carousel viewer is showing');
+    _label_minFontSize?: NUMt = new NumInfo('minimum font size for labelBoxes');
+    _label_maxFontSize?: NUMt = new NumInfo('maximum font size for labelBoxes');
+    stroke_width?: NUMt = new NumInfo('width of an ink stroke');
+    icon_label?: STRt = new StrInfo('label to use for a fontIcon doc (otherwise, the title is used)');
+    mediaState?: STRt = new StrInfo('status of audio/video media document: "pendingRecording", "recording", "paused", "playing"');
+    recording?: BOOLt = new BoolInfo('whether WebCam is recording or not');
+    autoPlayAnchors?: BOOLt = new BoolInfo('whether to play audio/video when an anchor is clicked in a stackedTimeline.');
+    dontPlayLinkOnSelect?: BOOLt = new BoolInfo('whether an audio/video should start playing when a link is followed to it.');
     updateContentsScript?: ScriptField; // reactive script invoked when viewing a document that can update contents of a collection (or do anything)
     toolTip?: string; // tooltip to display on hover
     toolType?: string; // type of pen tool
-    expertMode?: boolean; // something available only in expert (not novice) mode
+    expertMode?: BOOLt = new BoolInfo('something available only in expert (not novice) mode');
+
+    contentPointerEvents?: string; // 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
     contextMenuFilters?: List<ScriptField>;
     contextMenuScripts?: List<ScriptField>;
     contextMenuLabels?: List<string>;
     contextMenuIcons?: List<string>;
-    defaultDoubleClick?: 'ignore' | 'default'; // ignore double clicks, or deafult (undefined) means open document full screen
-    waitForDoubleClickToClick?: 'always' | 'never' | 'default'; // whether a click function wait for double click to expire.  'default' undefined = wait only if there's a click handler, "never" = never wait, "always" = alway wait
-    dontUndo?: boolean; // whether button clicks should be undoable ( true for Undo/Redo/and sidebar) AND whether modifications to document are undoable (true for linearview menu buttons to prevent open/close from entering undo stack)
-    layout?: string | Doc; // default layout string for a document
-    contentPointerEvents?: string; // 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
-    childLimitHeight?: number; // whether to limit the height of collection children.  0 - means  height can be no bigger than width
+    childFilters_boolean?: string;
+    childFilters?: List<string>;
+    childLimitHeight?: NUMt = new NumInfo('whether to limit the height of collection children.  0 - means  height can be no bigger than width');
     childLayoutTemplate?: Doc; // template for collection to use to render its children (see PresBox layout in tree view)
     childLayoutString?: string; // template string for collection to use to render its children
-    childDocumentsActive?: boolean; // whether child documents are active when parent is document active
-    childDontRegisterViews?: boolean;
-    childHideLinkButton?: boolean; // hide link buttons on all children
+    childDocumentsActive?: BOOLt = new BoolInfo('whether child documents are active when parent is document active');
+    childDontRegisterViews?: BOOLt = new BoolInfo('whether child document views should be registered so that they can be found when following links, etc');
+    childHideLinkButton?: BOOLt = new BoolInfo('hide link buttons on all children');
     childContextMenuFilters?: List<ScriptField>;
     childContextMenuScripts?: List<ScriptField>;
     childContextMenuLabels?: List<string>;
     childContextMenuIcons?: List<string>;
-    followLinkZoom?: boolean; // whether to zoom to the target of a link
-    layout_hideLinkButton?: boolean; // whether the blue link counter button should be hidden
-    disableDocBrushing?: boolean; // whether to suppress border highlighting
-    layout_hideDecorationTitle?: boolean;
-    hideOpenButton?: boolean;
-    layout_hideResizeHandles?: boolean;
-    hideDocumentButtonBar?: boolean;
-    hideAllLinks?: boolean; // whether all individual blue anchor dots should be hidden
-    isTemplateForField?: string; // the field key for which the containing document is a rendering template
-    isTemplateDoc?: boolean;
     targetScriptKey?: string; // where to write a template script (used by collections with click templates which need to target onClick, onDoubleClick, etc)
-    templates?: List<string>;
-    hero?: ImageField; // primary image that best represents a compound document (e.g., for a buxton device document that has multiple images)
-    caption?: RichTextField;
-    opacity?: number;
-    defaultBackgroundColor?: string;
-    _layout_autoMoveAnchors?: boolean; // whether link endpoint should move around the edges of a document to make shortest path to other link endpoint
-    layout_hideLinkAnchors?: boolean; // suppresses link anchor dots from being displayed
-    isFolder?: boolean;
-    lastFrame?: number; // the last frame of a frame-based collection (e.g., progressive slide)
-    activeFrame?: number; // the active frame of a document in a frame base collection
-    appearFrame?: number; // the frame in which the document appears
-    viewTransitionTime?: number; // transition duration for view parameters
-    presPanX?: number; // panX saved as a view spec
-    presPanY?: number; // panY saved as a view spec
-    presViewScale?: number; // viewScale saved as a view Spec
-    presTransition?: number; //the time taken for the transition TO a document
-    presDuration?: number; //the duration of the slide in presentation view
-    presZoomText?: boolean; // whether text anchors should shown in a larger box when following links to make them stand out
-    borderRounding?: string;
-    boxShadow?: string; // box-shadow css string OR "standard" to use dash standard box shadow
+
+    lastFrame?: NUMt = new NumInfo('the last frame of a frame-based collection (e.g., progressive slide)');
+    activeFrame?: NUMt = new NumInfo('the active frame of a document in a frame base collection');
+    appearFrame?: NUMt = new NumInfo('the frame in which the document appears');
+    _currentFrame?: NUMt = new NumInfo('the current frame of a frame-based collection (e.g., progressive slide)');
+
+    isSystem?: BOOLt = new BoolInfo('is this a system created/owned doc', true);
+    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');
+
+    presPanX?: NUMt = new NumInfo('panX saved as a view spec');
+    presPanY?: NUMt = new NumInfo('panY saved as a view spec');
+    presViewScale?: NUMt = new NumInfo('viewScale saved as a view Spec');
+    presTransition?: NUMt = new NumInfo('the time taken for the transition TO a document');
+    presDuration?: NUMt = new NumInfo('the duration of the slide in presentation view');
+    presZoomText?: BOOLt = new BoolInfo('whether text anchors should shown in a larger box when following links to make them stand out');
+
     data?: any;
-    isBaseProto?: boolean; // this Doc is 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
-    dontRegisterView?: boolean;
-    lookupField?: ScriptField; // script that returns the value of a field. This script is passed the rootDoc, layoutDoc, field, and container of the document.  see PresBox.
+    data_useCors?: BOOLt = new BoolInfo('whether CORS protocol should be used for web page');
     columnHeaders?: List<SchemaHeaderField>; // headers for stacking views
     schemaHeaders?: List<SchemaHeaderField>; // headers for schema view
-    dockingConfig?: string;
-    annotationOn?: Doc;
-    followLinkToggle?: boolean;
-    isGroup?: boolean; // whether a collection should use a grouping UI behavior
-    _removeDropProperties?: List<string>; // list of properties that should be removed from a document when it is dropped.  e.g., a creator button may be forceActive to allow it be dragged, but the forceActive property can be removed from the dropped document
+    dockingConfig?: STRt = new StrInfo('configuration of golden layout windows (applies only if doc is rendered as a CollectionDockingView)');
+    icon?: string; // icon used by fonticonbox to render button
     noteType?: string;
-    // BACKGROUND GRID
+
+    // freeform properties
     _freeform_backgroundGrid?: boolean;
+    _freeform_scale?: NUMt = new NumInfo('how much a freeform view has been scaled (zoomed)');
+    _freeform_panX?: NUMt = new NumInfo('horizontal pan location of a freeform view');
+    _freeform_panY?: NUMt = new NumInfo('vertical pan location of a freeform view');
+    _freeform_noAutoPan?: BOOLt = new BoolInfo('disables autopanning when this item is dragged');
+    _freeform_noZoom?: BOOLt = new BoolInfo('disables zooming');
 
     //BUTTONS
     buttonText?: string;
-    iconShape?: string; // shapes of the fonticon border
     btnType?: string;
     btnList?: List<string>;
     docColorBtn?: string;
     userColorBtn?: string;
-    canClick?: string;
     script?: ScriptField;
-    numBtnMax?: number;
-    numBtnMin?: number;
+    numBtnMax?: NUMt = new NumInfo('maximum value of a number button');
+    numBtnMin?: NUMt = new NumInfo('minimum value of a number button');
     switchToggle?: boolean;
     badgeValue?: ScriptField;
 
     //LINEAR VIEW
-    linearViewIsExpanded?: boolean; // is linear view expanded
-    linearViewExpandable?: boolean; // can linear view be expanded
-    linearViewToggleButton?: string; // button to open close linear view group
-    linearViewSubMenu?: boolean;
-    linearBtnWidth?: number;
-    flexGap?: number; // Linear view flex gap
+    linearView_IsExpanded?: BOOLt = new BoolInfo('is linear view expanded');
+    linearView_Expandable?: BOOLt = new BoolInfo('can linear view be expanded');
+    linearView_SubMenu?: BOOLt = new BoolInfo('is doc a sub menu of more linear views');
+    flexGap?: NUMt = new NumInfo('Linear view flex gap');
     flexDirection?: 'unset' | 'row' | 'column' | 'row-reverse' | 'column-reverse';
 
-    layout_linkView?: Doc; // view template for a link document
-    layout_keyValue?: string; // view tempalte for key value docs
     link_description?: string; // added for links
     link_relationship?: string; // type of relatinoship a link represents
-    layout_linkDisplay?: boolean; // whether a link line should be dipslayed between the two link anchors
-    layout_linkDisplayArrow?: boolean; // whether to display link's directional arrowhead
+    link_displayLine?: BOOLt = new BoolInfo('whether a link line should be dipslayed between the two link anchors');
+    link_displayArrow?: BOOLt = new BoolInfo("whether to display link's directional arrowhead");
     link_anchor_1?: Doc;
     link_anchor_2?: Doc;
-    link_anchor_1_useLinkSmallAnchor?: boolean; // whether link_anchor_1 of a link should use a miniature anchor dot (as when the anchor is a text selection)
-    link_anchor_2_useLinkSmallAnchor?: boolean; // whether link_anchor_1 of a link should use a miniature anchor dot (as when the anchor is a text selection)
-    ignoreClick?: boolean;
+    link_autoMoveAnchors?: BOOLt = new BoolInfo('whether link endpoint should move around the edges of a document to make shortest path to other link endpoint');
+    link_anchor_1_useSmallAnchor?: BOOLt = new BoolInfo('whether link_anchor_1 of a link should use a miniature anchor dot (as when the anchor is a text selection)');
+    link_anchor_2_useSmallAnchor?: BOOLt = new BoolInfo('whether link_anchor_1 of a link should use a miniature anchor dot (as when the anchor is a text selection)');
+    link_relationshipList?: List<string>; // for storing different link relationships (when set by user in the link editor)
+    link_relationshipSizes?: List<number>; //stores number of links contained in each relationship
+    link_colorList?: List<string>; // colors of links corresponding to specific link relationships
+    followLinkZoom?: BOOLt = new BoolInfo('whether to zoom to the target of a link');
+    followLinkToggle?: BOOLt = new BoolInfo('whether target of link should be toggled on and off when following a link to it');
+    followLinkLocation?: STRt = new StrInfo('where to open link target when following link');
+    followLinkAnimEffect?: STRt = new StrInfo('animation effect triggered on target of link');
+    followLinkAnimDirection?: STRt = new StrInfo('direction modifier for animation effect');
+
+    ignoreClick?: BOOLt = new BoolInfo('whether clicks on document should be ignored');
     onClick?: ScriptField;
     onDoubleClick?: ScriptField;
     onChildClick?: ScriptField; // script given to children of a collection to execute when they are clicked
     onChildDoubleClick?: ScriptField; // script given to children of a collection to execute when they are double clicked
+    defaultDoubleClick?: 'ignore' | 'default'; // ignore double clicks, or deafult (undefined) means open document full screen
+    waitForDoubleClickToClick?: 'always' | 'never' | 'default'; // whether a click function wait for double click to expire.  'default' undefined = wait only if there's a click handler, "never" = never wait, "always" = alway wait
     onPointerDown?: ScriptField;
     onPointerUp?: ScriptField;
+    dragFactory_count?: NUMt = new NumInfo('number of items created from a drag button (used for setting title with incrementing index)', true);
+    openFactoryLocation?: string; // an OpenWhere value to place the factory created document
+    openFactoryAsDelegate?: BOOLt = new BoolInfo('create a delegate of the factory');
+    _forceActive?: BOOLt = new BoolInfo('flag to handle pointer events when not selected (or otherwise active)');
+    enableDragWhenActive?: BOOLt = new BoolInfo('allow dragging even if document contentts are active (e.g., tree, groups)');
+    _stayInCollection?: BOOLt = new BoolInfo('whether the document should remain in its collection when someone tries to drag and drop it elsewhere');
+    _raiseWhenDragged?: BOOLt = new BoolInfo('whether a document is brought to front when dragged.');
+    allowOverlayDrop?: BOOLt = new BoolInfo('can documents be dropped onto this document without using dragging title bar or holding down embed key (ctrl)?', true);
+    childDropAction?: DROPt = new DAInfo("what should happen to the source document when it's dropped onto a child of a collection ");
+    targetDropAction?: DROPt = new DAInfo('what should happen to the source document when ??? ');
+    _dropAction?: DROPt = new DAInfo("what should happen to this document when it's dropped somewhere else");
+    _removeDropProperties?: List<string>; // list of properties that should be removed from a document when it is dropped.  e.g., a creator button may be forceActive to allow it be dragged, but the forceActive property can be removed from the dropped document
+    cloneFieldFilter?: List<string>; // fields not to copy when the document is clonedclipboard?: Doc;
     dropConverter?: ScriptField; // script to run when documents are dropped on this Document.
     dragFactory?: Doc; // document to create when dragging with a suitable onDragStart script
     clickFactory?: Doc; // document to create when clicking on a button with a suitable onClick script
     onDragStart?: ScriptField; //script to execute at start of drag operation --  e.g., when a "creator" button is dragged this script generates a different document to drop
-    cloneFieldFilter?: List<string>; // fields not to copy when the document is clonedclipboard?: Doc;
-    filterBoolean?: string;
-    data_useCors?: boolean;
-    icon?: string; // icon used by fonticonbox to render button
-    target?: Doc; // available for use in scripts as the primary target document
-    sourcePanel?: Doc; // panel to display in 'targetContainer' as the result of a button onClick script
-    targetContainer?: Doc; // document whose proto will be set to 'panel' as the result of a onClick click script
-    searchFileTypes?: List<string>; // file types allowed in a search query
-    stroke_width?: number;
-    freezeChildren?: string; // whether children are now allowed to be added and or removed from a collection
-    treeViewHideTitle?: boolean; // whether to hide the top document title of a tree view
-    treeViewHideUnrendered?: boolean; // tells tree view not to display documents that have an 'layout_unrendered' tag unless they also have a treeViewFieldKey tag (presBox)
-    treeViewHideHeaderIfTemplate?: boolean; // whether to hide the header for a document in a tree view only if a childLayoutTemplate is provided (presBox)
-    treeViewHideHeader?: boolean; // whether to hide the header for a document in a tree view
-    treeViewHideHeaderFields?: boolean; // whether to hide the drop down options for tree view items.
+    target?: Doc; // available for use in scripts.  used to provide a document parameter to the script (Note, this is a convenience entry since any field could be used for parameterizing a script)
+
+    treeViewHideTitle?: BOOLt = new BoolInfo('whether to hide the top document title of a tree view');
+    treeViewHideUnrendered?: BOOLt = new BoolInfo("tells tree view not to display documents that have an 'layout_unrendered' tag unless they also have a treeViewFieldKey tag (presBox)");
+    treeViewHideHeaderIfTemplate?: BOOLt = new BoolInfo('whether to hide the header for a document in a tree view only if a childLayoutTemplate is provided (presBox)');
+    treeViewHideHeader?: BOOLt = new BoolInfo('whether to hide the header for a document in a tree view');
+    treeViewHideHeaderFields?: BOOLt = new BoolInfo('whether to hide the drop down options for tree view items.');
     treeViewChildDoubleClick?: ScriptField; //
-    // Action Button
-    buttonMenu?: boolean; // whether a action button should be displayed
-    buttonMenuDoc?: Doc;
-    explainer?: string;
-
-    treeViewOpenIsTransient?: boolean; // ignores the treeViewOpen Doc flag, allowing a treeViewItem's expand/collapse state to be independent of other views of the same document in the same or any other tree view
-    _treeViewOpen?: boolean; // whether this document is expanded in a tree view  (note: need _ and regular versions since this can be specified for both proto and layout docs)
-    treeViewOpen?: boolean; // whether this document is expanded in a tree view
+    treeViewOpenIsTransient?: BOOLt = new BoolInfo("ignores the treeViewOpen Doc flag, allowing a treeViewItem's expand/collapse state to be independent of other views of the same document in the same or any other tree view");
+    treeViewOpen?: BOOLt = new BoolInfo('whether this document is expanded in a tree view');
     treeViewExpandedView?: string; // which field/thing is displayed when this item is opened in tree view
-    treeViewExpandedViewLock?: boolean; // whether the expanded view can be changed
+    treeViewExpandedViewLock?: BOOLt = new BoolInfo('whether the expanded view can be changed');
     treeViewChecked?: ScriptField; // script to call when a tree view checkbox is checked
-    treeViewTruncateTitleWidth?: number;
-    treeViewHasOverlay?: boolean; // whether the treeview has an overlay for freeform annotations
+    treeViewTruncateTitleWidth?: NUMt = new NumInfo('maximum width of a treew view title before truncation');
+    treeViewHasOverlay?: BOOLt = new BoolInfo('whether the treeview has an overlay for freeform annotations');
     treeViewType?: string; // whether treeview is a Slide, file system, or (default) collection hierarchy
-    sidebarColor?: string; // background color of text sidebar
-    sidebarViewType?: string; // collection type of text sidebar
-    docMaxAutoHeight?: number; // maximum height for newly created (eg, from pasting) text documents
+    treeViewFreezeChildren?: STRt = new StrInfo('set (add, remove, add|remove) to disable adding, removing or both from collection');
+
+    sidebar_color?: string; // background color of text sidebar
+    sidebar_collectionType?: string; // collection type of text sidebar
+
     text?: string;
-    textTransform?: string; // is linear view expanded
-    letterSpacing?: string; // is linear view expanded
+    textTransform?: string;
+    letterSpacing?: string;
     iconTemplate?: string; // name of icon template style
-    selectedIndex?: number; // which item in a linear view has been selected using the "thumb doc" ui
+    selectedIndex?: NUMt = new NumInfo("which item in a linear view has been selected using the 'thumb doc' ui");
+
+    fieldValues?: List<any>; // possible values a field can have (used by FieldInfo's only)
+    fieldType?: string; // display type of a field, e.g. string, number, enumeration (used by FieldInfo's only)
+
     clipboard?: Doc;
-    searchQuery?: string; // for quersyBox
-    useLinkSmallAnchor?: boolean; // whether links to this document should use a miniature linkAnchorBox
-    border?: string; //for searchbox
     hoverBackgroundColor?: string; // background color of a label when hovered
-    link_relationshipList?: List<string>; // for storing different link relationships (when set by user in the link editor)
-    link_relationshipSizes?: List<number>; //stores number of links contained in each relationship
-    linkColorList?: List<string>; // colors of links corresponding to specific link relationships
+    userColor?: STRt = new StrInfo('color associated with a Dash user (seen in header fields of shared documents)');
 }
 export namespace Docs {
     export let newAccount: boolean = false;
@@ -986,7 +991,7 @@ export namespace Docs {
             I.layout = InkingStroke.LayoutString('stroke');
             I.layout_fitWidth = true;
             I.layout_hideDecorationTitle = true; // don't show title when selected
-            // I.hideOpenButton = true; // don't show open full screen button when selected
+            // I.layout_hideOpenButton = true; // don't show open full screen button when selected
             I.color = color;
             I.fillColor = fillColor;
             I.stroke = new InkField(points);
@@ -1088,7 +1093,12 @@ export namespace Docs {
         }
 
         export function PileDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _overflow: 'visible', enableDragWhenActive: true, _forceActive: true, _noAutoscroll: true, ...options, _type_collection: CollectionViewType.Pile }, id);
+            return InstanceFromProto(
+                Prototypes.get(DocumentType.COL),
+                new List(documents),
+                { enableDragWhenActive: true, _forceActive: true, _freeform_noZoom: true, _freeform_noAutoPan: true, ...options, _type_collection: CollectionViewType.Pile },
+                id
+            );
         }
 
         export function LinearDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
@@ -1177,12 +1187,7 @@ export namespace Docs {
         }
 
         export function DockDocument(documents: Array<Doc>, config: string, options: DocumentOptions, id?: string) {
-            return InstanceFromProto(
-                Prototypes.get(DocumentType.COL),
-                new List(documents),
-                { freezeChildren: 'remove|add', ...options, type_collection: CollectionViewType.Docking, _type_collection: CollectionViewType.Docking, dockingConfig: config },
-                id
-            );
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { treeViewFreezeChildren: 'remove|add', ...options, _type_collection: CollectionViewType.Docking, dockingConfig: config }, id);
         }
 
         export function DirectoryImportDocument(options: DocumentOptions = {}) {
@@ -1227,18 +1232,18 @@ export namespace Docs {
 export namespace DocUtils {
     /**
      * @param docs
-     * @param docFilters
-     * @param docRangeFilters
+     * @param childFilters
+     * @param childFiltersByRanges
      * @param parentCollection
-     * Given a list of docs and docFilters, @returns the list of Docs that match those filters
+     * Given a list of docs and childFilters, @returns the list of Docs that match those filters
      */
-    export function FilterDocs(childDocs: Doc[], docFilters: string[], docRangeFilters: string[], parentCollection?: Doc) {
-        if (!docFilters?.length && !docRangeFilters?.length) {
+    export function FilterDocs(childDocs: Doc[], childFilters: string[], childFiltersByRanges: string[], parentCollection?: Doc) {
+        if (!childFilters?.length && !childFiltersByRanges?.length) {
             return childDocs.filter(d => !d.cookies); // remove documents that need a cookie if there are no filters to provide one
         }
 
         const filterFacets: { [key: string]: { [value: string]: string } } = {}; // maps each filter key to an object with value=>modifier fields
-        docFilters.forEach(filter => {
+        childFilters.forEach(filter => {
             const fields = filter.split(':');
             const key = fields[0];
             const value = fields[1];
@@ -1249,7 +1254,7 @@ export namespace DocUtils {
             filterFacets[key][value] = modifiers;
         });
 
-        const filteredDocs = docFilters.length
+        const filteredDocs = childFilters.length
             ? childDocs.filter(d => {
                   if (d.z) return true;
                   // if the document needs a cookie but no filter provides the cookie, then the document does not pass the filter
@@ -1293,7 +1298,7 @@ export namespace DocUtils {
                                 return Field.toString(d[facetKey] as Field).includes(value);
                             });
                       // if we're ORing them together, the default return is false, and we return true for a doc if it satisfies any one set of criteria
-                      if (parentCollection?.filterBoolean === 'OR') {
+                      if (parentCollection?.childFilters_boolean === 'OR') {
                           if (satisfiesUnsetsFacets && satisfiesExistsFacets && satisfiesCheckFacets && !failsNotEqualFacets && satisfiesMatchFacets) return true;
                       }
                       // if we're ANDing them together, the default return is true, and we return false for a doc if it doesn't satisfy any set of criteria
@@ -1301,14 +1306,14 @@ export namespace DocUtils {
                           if (!satisfiesUnsetsFacets || !satisfiesExistsFacets || !satisfiesCheckFacets || failsNotEqualFacets || (matches.length && !satisfiesMatchFacets)) return false;
                       }
                   }
-                  return (parentCollection?.currentFilter as Doc)?.filterBoolean === 'OR' ? false : true;
+                  return (parentCollection?.currentFilter as Doc)?.childFilters_boolean === 'OR' ? false : true;
               })
             : childDocs;
         const rangeFilteredDocs = filteredDocs.filter(d => {
-            for (let i = 0; i < docRangeFilters.length; i += 3) {
-                const key = docRangeFilters[i];
-                const min = Number(docRangeFilters[i + 1]);
-                const max = Number(docRangeFilters[i + 2]);
+            for (let i = 0; i < childFiltersByRanges.length; i += 3) {
+                const key = childFiltersByRanges[i];
+                const min = Number(childFiltersByRanges[i + 1]);
+                const max = Number(childFiltersByRanges[i + 2]);
                 const val = typeof d[key] === 'string' ? (Number(StrCast(d[key])).toString() === StrCast(d[key]) ? Number(StrCast(d[key])) : undefined) : Cast(d[key], 'number', null);
                 if (val === undefined) {
                     //console.log("Should 'undefined' pass range filter or not?")
@@ -1325,11 +1330,11 @@ export namespace DocUtils {
         broadcastEvent && runInAction(() => (DocumentManager.Instance.RecordingEvent = DocumentManager.Instance.RecordingEvent + 1));
         return DocUtils.ActiveRecordings.map(audio => {
             const sourceDoc = getSourceDoc();
-            return sourceDoc && DocUtils.MakeLink(sourceDoc, audio.getAnchor(true) || audio.props.Document, { layout_linkDisplay: false, link_relationship: 'recording annotation:linked recording', link_description: 'recording timeline' });
+            return sourceDoc && DocUtils.MakeLink(sourceDoc, audio.getAnchor(true) || audio.props.Document, { link_displayLine: false, link_relationship: 'recording annotation:linked recording', link_description: 'recording timeline' });
         });
     }
 
-    export function MakeLink(source: Doc, target: Doc, linkSettings: { link_relationship?: string; link_description?: string; layout_linkDisplay?: boolean }, id?: string, showPopup?: number[]) {
+    export function MakeLink(source: Doc, target: Doc, linkSettings: { link_relationship?: string; link_description?: string; link_displayLine?: boolean }, id?: string, showPopup?: number[]) {
         if (!linkSettings.link_relationship) linkSettings.link_relationship = target.type === DocumentType.RTF ? 'Commentary:Comments On' : 'link';
         const sv = DocumentManager.Instance.getDocumentView(source);
         if (target.doc === Doc.UserDoc()) return undefined;
@@ -1370,15 +1375,15 @@ export namespace DocUtils {
                 source,
                 target,
                 {
-                    title: ComputedField.MakeFunction('generateLinkTitle(self)') as any,
-                    link_anchor_1_useLinkSmallAnchor: source.useLinkSmallAnchor ? true : undefined,
-                    link_anchor_2_useLinkSmallAnchor: target.useLinkSmallAnchor ? true : undefined,
                     'acl-Public': SharingPermissions.Augment,
                     '_acl-Public': SharingPermissions.Augment,
-                    layout_linkDisplay: linkSettings.layout_linkDisplay,
+                    title: ComputedField.MakeFunction('generateLinkTitle(self)') as any,
+                    link_anchor_1_useSmallAnchor: source.useSmallAnchor ? true : undefined,
+                    link_anchor_2_useSmallAnchor: target.useSmallAnchor ? true : undefined,
+                    link_displayLine: linkSettings.link_displayLine,
                     link_relationship: linkSettings.link_relationship,
                     link_description: linkSettings.link_description,
-                    _layout_autoMoveAnchors: true,
+                    link_autoMoveAnchors: true,
                     _layout_showCaption: 'link_description',
                     _layout_showTitle: 'link_relationship',
                 },
@@ -1706,14 +1711,14 @@ export namespace DocUtils {
         if (context && !hasContextAnchor && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG)) {
             const pushpin = Docs.Create.FontIconDocument({
                 title: 'pushpin',
-                label: '',
+                icon_label: '',
                 annotationOn: Cast(doc.annotationOn, Doc, null),
                 followLinkToggle: true,
                 icon: 'map-pin',
                 x: Cast(doc.x, 'number', null),
                 y: Cast(doc.y, 'number', null),
                 backgroundColor: '#ACCEF7',
-                hideAllLinks: true,
+                layout_hideAllLinks: true,
                 _width: 15,
                 _height: 15,
                 _xPadding: 0,
@@ -1804,7 +1809,7 @@ export namespace DocUtils {
             _xMargin: noMargins ? 0 : undefined,
             _yMargin: noMargins ? 0 : undefined,
             annotationOn,
-            docMaxAutoHeight: maxHeight,
+            layout_maxAutoHeight: maxHeight,
             backgroundColor,
             _width: width || 200,
             _height: 35,
@@ -1812,7 +1817,7 @@ export namespace DocUtils {
             y: y,
             _layout_fitWidth: true,
             _layout_autoHeight: true,
-            _layout_altContentUI: BoolCast(Doc.UserDoc().defaultToFlashcards),
+            _layout_enableAltContentUI: BoolCast(Doc.UserDoc().defaultToFlashcards),
             title,
         });
         const template = Doc.UserDoc().defaultTextLayout;
@@ -1842,18 +1847,18 @@ export namespace DocUtils {
     }
 
     /**
-     * uploadFilesToDocs will take in an array of Files, and creates documents for the 
-     * new files. 
-     * 
+     * uploadFilesToDocs will take in an array of Files, and creates documents for the
+     * new files.
+     *
      * @param files an array of files that will be uploaded
      * @param options options to use while uploading
-     * @returns 
+     * @returns
      */
     export async function uploadFilesToDocs(files: File[], options: DocumentOptions) {
         const generatedDocuments: Doc[] = [];
 
-        // These files do not have overwriteDocs, so we do not set the guid and let the client generate one. 
-        const fileNoGuidPairs: Networking.FileGuidPair[] = files.map(file => ({file}));
+        // These files do not have overwriteDocs, so we do not set the guid and let the client generate one.
+        const fileNoGuidPairs: Networking.FileGuidPair[] = files.map(file => ({ file }));
 
         const upfiles = await Networking.UploadFilesToServer(fileNoGuidPairs);
         for (const {
@@ -1867,8 +1872,8 @@ export namespace DocUtils {
 
     export function uploadFileToDoc(file: File, options: DocumentOptions, overwriteDoc: Doc) {
         const generatedDocuments: Doc[] = [];
-        // Since this file has an overwriteDoc, we can set the client tracking guid to the overwriteDoc's guid. 
-        Networking.UploadFilesToServer([{file, guid: overwriteDoc[Id]}]).then(upfiles => {
+        // Since this file has an overwriteDoc, we can set the client tracking guid to the overwriteDoc's guid.
+        Networking.UploadFilesToServer([{ file, guid: overwriteDoc[Id] }]).then(upfiles => {
             const {
                 source: { name, type },
                 result,
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 6f6ac2d4a..59041862f 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -23,7 +23,7 @@ import { MainView } from "../views/MainView";
 import { ButtonType } from "../views/nodes/button/FontIconBox";
 import { OpenWhere } from "../views/nodes/DocumentView";
 import { OverlayView } from "../views/OverlayView";
-import { DragManager } from "./DragManager";
+import { DragManager, dropActionType } from "./DragManager";
 import { MakeTemplate } from "./DropConverter";
 import { FollowLinkScript } from "./LinkFollower";
 import { LinkManager } from "./LinkManager";
@@ -97,7 +97,7 @@ export class CurrentUserUtils {
 
         const reqdOpts:DocumentOptions = {
             title: "Experimental Tools", _xMargin: 0, _layout_showTitle: "title", _chromeHidden: true,
-            _stayInCollection: true, _hideContextMenu: true, _forceActive: true, isSystem: true,
+            _stayInCollection: true, _layout_hideContextMenu: true, _forceActive: true, isSystem: true,
             _layout_autoHeight: true, _width: 500, _height: 300, _layout_fitWidth: true, _columnWidth: 35, ignoreClick: true, _lockedPosition: true,
         };
         const reqdScripts = { dropConverter : "convertToButtons(dragData)" };
@@ -136,7 +136,7 @@ export class CurrentUserUtils {
         const reqdClickList = reqdTempOpts.map(opts => {
             const allOpts = {...reqdClickOpts, ...opts.opts};
             const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(doc => doc.title === opts.opts.title): undefined;
-            return DocUtils.AssignOpts(clickDoc, allOpts) ?? MakeTemplate(Docs.Create.ScriptingDocument(ScriptField.MakeScript(opts.script, {heading:Doc.name, checked:"boolean", containingTreeView:Doc.name}), allOpts), true, opts.opts.title);
+            return DocUtils.AssignOpts(clickDoc, allOpts) ?? MakeTemplate(Docs.Create.ScriptingDocument(ScriptField.MakeScript(opts.script, {heading:Doc.name, checked:"boolean", containingTreeView:Doc.name}), allOpts), true, opts.opts.title?.toString());
         });
 
         const reqdOpts:DocumentOptions = {title: "click editor templates", _height:75, isSystem: true};
@@ -192,7 +192,7 @@ export class CurrentUserUtils {
                 {onClick:"deiconifyView(documentView)", onDoubleClick: "deiconifyViewToLightbox(documentView)", });
         };
         const labelBox = (opts: DocumentOptions, data?:string) => Docs.Create.LabelDocument({
-            textTransform: "unset", letterSpacing: "unset", _singleLine: false, _minFontSize: 14, _maxFontSize: 24, borderRounding: "5px",  _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, ...opts
+            textTransform: "unset", letterSpacing: "unset", _singleLine: false, _label_minFontSize: 14, _label_maxFontSize: 24, layout_borderRounding: "5px",  _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, ...opts
         });
         const imageBox = (opts: DocumentOptions, url?:string) => Docs.Create.ImageDocument(url ?? "http://www.cs.brown.edu/~bcz/noImage.png", { "icon_nativeWidth": 360 / 4, "icon_nativeHeight": 270 / 4, iconTemplate:DocumentType.IMG,  _width: 360 / 4, _height: 270 / 4,  _layout_showTitle: "title", ...opts });
         const fontBox = (opts:DocumentOptions, data?:string) => Docs.Create.FontIconDocument({ _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, ...opts  });
@@ -260,10 +260,11 @@ export class CurrentUserUtils {
         const emptyThings:{key:string,  // the field name where the empty thing will be stored
                           opts:DocumentOptions,   // the document options that are required for the empty thing
                           funcs?:{[key:string]: any},  // computed fields that are rquired for the empth thing
+                          scripts?:{[key:string]: any},
                           creator:(opts:DocumentOptions)=> any // how to create the empty thing if it doesn't exist
               }[] = [
             {key: "Note",       creator: opts => Docs.Create.TextDocument("", opts),        opts: { _width: 200, _layout_autoHeight: true }},
-            {key: "Flashcard",  creator: opts => Docs.Create.TextDocument("", opts),        opts: { _width: 200, _layout_autoHeight: true, _layout_altContentUI: true}},
+            {key: "Flashcard",  creator: opts => Docs.Create.TextDocument("", opts),        opts: { _width: 200, _layout_autoHeight: true, _layout_enableAltContentUI: true}},
             {key: "Equation",   creator: opts => Docs.Create.EquationDocument(opts),        opts: { _width: 300, _height: 35, }},
             {key: "Noteboard",  creator: opts => Docs.Create.NoteTakingDocument([], opts),  opts: { _width: 250, _height: 200, _layout_fitWidth: true}},
             {key: "Simulation", creator: opts => Docs.Create.SimulationDocument(opts),      opts: { _width: 300, _height: 300, }},
@@ -274,11 +275,11 @@ export class CurrentUserUtils {
             {key: "Map",        creator: opts => Docs.Create.MapDocument([], opts),         opts: { _width: 800, _height: 600, _layout_fitWidth: true, _layout_showSidebar: 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, onClick: FollowLinkScript()}},
+            {key: "Button",     creator: Docs.Create.ButtonDocument,                        opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10}, scripts: {onClick: FollowLinkScript()?.script.originalScript ?? ""}},
             {key: "Script",     creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }},
             {key: "DataViz",    creator: opts => Docs.Create.DataVizDocument("/users/rz/Downloads/addresses.csv", opts),   opts: { _width: 300, _height: 300 }},
             {key: "Header",     creator: headerTemplate,                                    opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _layout_autoHeight: true, treeViewHideUnrendered: true}},
-            {key: "Trail",      creator: Docs.Create.PresDocument,                          opts: { _width: 400, _height: 30, _type_collection: CollectionViewType.Stacking, targetDropAction: "embed" as any, treeViewHideTitle: true, _layout_fitWidth:true, _chromeHidden: true, boxShadow: "0 0" }},
+            {key: "Trail",      creator: Docs.Create.PresDocument,                          opts: { _width: 400, _height: 30, _type_collection: CollectionViewType.Stacking, targetDropAction: "embed" as dropActionType, treeViewHideTitle: true, _layout_fitWidth:true, _chromeHidden: true, layout_boxShadow: "0 0" }},
             {key: "Tab",        creator: opts => Docs.Create.FreeformDocument([], opts),    opts: { _width: 500, _height: 800, _layout_fitWidth: true, _freeform_backgroundGrid: true, }},
             {key: "Slide",      creator: opts => Docs.Create.TreeDocument([], opts),        opts: { _width: 300, _height: 200, _type_collection: CollectionViewType.Tree, 
                                                                                                 treeViewHasOverlay: true, _text_fontSize: "20px", _layout_autoHeight: true, 
@@ -287,7 +288,7 @@ export class CurrentUserUtils {
                                                                                             }, funcs: {title: 'self.text?.Text'}},
             ];
 
-        emptyThings.forEach(thing => DocUtils.AssignDocField(doc, "empty"+thing.key, (opts) => thing.creator(opts), {...standardOps(thing.key), ...thing.opts}, undefined, undefined, thing.funcs));
+        emptyThings.forEach(thing => DocUtils.AssignDocField(doc, "empty"+thing.key, (opts) => thing.creator(opts), {...standardOps(thing.key), ...thing.opts}, undefined, thing.scripts, thing.funcs));
         
         return [
             { toolTip: "Tap or drag to create a note",            title: "Note", icon: "sticky-note",   dragFactory: doc.emptyNote as Doc,       clickFactory: DocCast(doc.emptyNote)},
@@ -321,7 +322,7 @@ export class CurrentUserUtils {
         const creatorBtns = CurrentUserUtils.creatorBtnDescriptors(doc).map((reqdOpts) => {
             const btn = dragCreatorDoc ? DocListCast(dragCreatorDoc.data).find(doc => doc.title === reqdOpts.title): undefined;
             const opts:DocumentOptions = {...OmitKeys(reqdOpts, ["funcs", "scripts", "backgroundColor"]).omit, 
-                _width: 35, _height: 35, _hideContextMenu: true, _stayInCollection: true, 
+                _width: 35, _height: 35, _layout_hideContextMenu: true, _stayInCollection: true, 
                 btnType: ButtonType.ToolButton, backgroundColor: reqdOpts.backgroundColor ?? Colors.DARK_GRAY, color: Colors.WHITE, isSystem: true,
                 _removeDropProperties: new List<string>(["_stayInCollection"]),
                 };
@@ -329,7 +330,7 @@ export class CurrentUserUtils {
         });
 
         const reqdOpts:DocumentOptions = {
-            title: "Basic Item Creators", _layout_showTitle: "title", _xMargin: 0, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, isSystem: true,
+            title: "Basic Item Creators", _layout_showTitle: "title", _xMargin: 0, _stayInCollection: true, _layout_hideContextMenu: true, _chromeHidden: true, isSystem: true,
             _layout_autoHeight: true, _width: 500, _height: 300, _layout_fitWidth: true, _columnWidth: 40, ignoreClick: true, _lockedPosition: true, _forceActive: true,
              childDropAction: 'embed' 
         };
@@ -367,14 +368,14 @@ export class CurrentUserUtils {
             const btnDoc = myLeftSidebarMenu ? DocListCast(myLeftSidebarMenu.data).find(doc => doc.title === title) : undefined;
             const reqdBtnOpts:DocumentOptions = {
                 title, icon, target, btnType: ButtonType.MenuButton, isSystem: true, dontUndo: true, dontRegisterView: true,
-                _width: 60, _height: 60,  _stayInCollection: true, _hideContextMenu: true,
+                _width: 60, _height: 60,  _stayInCollection: true, _layout_hideContextMenu: true,
                 _removeDropProperties: new List<string>(["_stayInCollection"]),
             };
             return DocUtils.AssignScripts(DocUtils.AssignOpts(btnDoc, reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), scripts, funcs);
         });
 
         const reqdStackOpts:DocumentOptions ={
-            title: "menuItemPanel", childDropAction: "same", backgroundColor: Colors.DARK_GRAY, boxShadow: "rgba(0,0,0,0)",  dontRegisterView: true, ignoreClick: true,
+            title: "menuItemPanel", childDropAction: "same", backgroundColor: Colors.DARK_GRAY, layout_boxShadow: "rgba(0,0,0,0)",  dontRegisterView: true, ignoreClick: true,
             _chromeHidden: true, _gridGap: 0, _yMargin: 0, _yPadding: 0, _xMargin: 0, _layout_autoHeight: false, _width: 60, _columnWidth: 60, _lockedPosition: true, isSystem: true,
         };
         return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdStackOpts, menuBtns, { dropConverter: "convertToButtons(dragData)" });
@@ -413,14 +414,14 @@ export class CurrentUserUtils {
     static mobileButton = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.MulticolumnDocument(docs, {
         ...opts,
          _nativeWidth: 900, _nativeHeight: 250, _width: 900, _height: 250, _yMargin: 15,
-        borderRounding: "5px", boxShadow: "0 0", isSystem: true
+        layout_borderRounding: "5px", layout_boxShadow: "0 0", isSystem: true
     }) as any as Doc
 
     // sets up the text container for the information contained within the mobile button
     static mobileTextContainer = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.MultirowDocument(docs, {
         ...opts,
         _nativeWidth: 450, _nativeHeight: 250, _width: 450, _height: 250, _yMargin: 25,
-        backgroundColor: "rgba(0,0,0,0)", borderRounding: "0", boxShadow: "0 0", ignoreClick: true, isSystem: true
+        backgroundColor: "rgba(0,0,0,0)", layout_borderRounding: "0", layout_boxShadow: "0 0", ignoreClick: true, isSystem: true
     }) as any as Doc
 
     // Sets up the title of the button
@@ -458,7 +459,7 @@ export class CurrentUserUtils {
     static setupSearcher(doc: Doc, field:string) {
         return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.SearchDocument(opts), {
                 dontRegisterView: true, backgroundColor: "dimgray", ignoreClick: true, title: "Search Panel", isSystem: true, childDropAction: "embed", 
-                _lockedPosition: true, _type_collection: CollectionViewType.Schema, _searchDoc: true,  });
+                _lockedPosition: true, _type_collection: CollectionViewType.Schema  });
     }
 
     /// Initializes the panel of draggable tools that is opened from the left sidebar.
@@ -467,8 +468,8 @@ export class CurrentUserUtils {
         const creatorBtns = CurrentUserUtils.setupCreatorButtons(doc, DocListCast(myTools?.data)?.length ? DocListCast(myTools.data)[0]:undefined);
         const templateBtns = CurrentUserUtils.setupExperimentalTemplateButtons(doc,DocListCast(myTools?.data)?.length > 1 ? DocListCast(myTools.data)[1]:undefined);
         const reqdToolOps:DocumentOptions = {
-            title: "My Tools", isSystem: true, ignoreClick: true, boxShadow: "0 0",
-            _layout_showTitle: "title", _width: 500, _yMargin: 20, _lockedPosition: true, _forceActive: true, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, 
+            title: "My Tools", isSystem: true, ignoreClick: true, layout_boxShadow: "0 0",
+            _layout_showTitle: "title", _width: 500, _yMargin: 20, _lockedPosition: true, _forceActive: true, _stayInCollection: true, _layout_hideContextMenu: true, _chromeHidden: true, 
         };
         return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdToolOps, [creatorBtns, templateBtns]);
     }
@@ -480,10 +481,10 @@ export class CurrentUserUtils {
         const toggleDarkTheme = `this.colorScheme = this.colorScheme ? undefined : "${ColorScheme.Dark}"`;
         const newDashboard = `createNewDashboard()`;
 
-        const reqdBtnOpts:DocumentOptions = { _forceActive: true,  _width: 30, _height: 30, _stayInCollection: true, _hideContextMenu: true,
+        const reqdBtnOpts:DocumentOptions = { _forceActive: true,  _width: 30, _height: 30, _stayInCollection: true, _layout_hideContextMenu: true,
              title: "new dashboard", btnType: ButtonType.ClickButton, toolTip: "Create new dashboard", buttonText: "New trail", icon: "plus", isSystem: true };
         const reqdBtnScript = {onClick: newDashboard,}
-        const newDashboardButton = DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(myDashboards?.buttonMenuDoc), reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), reqdBtnScript);
+        const newDashboardButton = DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(myDashboards?.layout_headerButton), reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), reqdBtnScript);
        
         const contextMenuScripts = [/*newDashboard*/] as string[];
         const contextMenuLabels = [/*"Create New Dashboard"*/]  as string[];
@@ -493,15 +494,15 @@ export class CurrentUserUtils {
         const childContextMenuLabels =  ["Toggle Dark Theme", "Toggle Comic Mode", "Snapshot Dashboard", "Share Dashboard", "Remove Dashboard", "Reset Dashboard"];// entries must be kept in synch with childContextMenuScripts, childContextMenuIcons, and childContextMenuFilters
         const childContextMenuIcons = ["chalkboard", "tv", "camera", "users", "times", "trash"]; // entries must be kept in synch with childContextMenuScripts, childContextMenuLabels, and childContextMenuFilters
         const reqdOpts:DocumentOptions = {
-            title: "My Dashboards", childHideLinkButton: true, freezeChildren: "remove|add", treeViewHideTitle: true, boxShadow: "0 0", childDontRegisterViews: true,
+            title: "My Dashboards", childHideLinkButton: true, treeViewFreezeChildren: "remove|add", treeViewHideTitle: true, layout_boxShadow: "0 0", childDontRegisterViews: true,
             targetDropAction: "same", treeViewType: TreeViewType.fileSystem, isFolder: true, isSystem: true,  treeViewTruncateTitleWidth: 350, ignoreClick: true, 
-            buttonMenu: true, buttonMenuDoc: newDashboardButton, childDropAction: "embed",
+            layout_headerButton: newDashboardButton, childDropAction: "embed",
             _layout_showTitle: "title", _height: 400, _gridGap: 5, _forceActive: true, _lockedPosition: true,
             contextMenuLabels:new List<string>(contextMenuLabels),
             contextMenuIcons:new List<string>(contextMenuIcons),
             childContextMenuLabels:new List<string>(childContextMenuLabels),
             childContextMenuIcons:new List<string>(childContextMenuIcons),
-            explainer: "This is your collection of dashboards. A dashboard represents the tab configuration of your workspace. To manage documents as folders, go to the Files."
+            layout_explainer: "This is your collection of dashboards. A dashboard represents the tab configuration of your workspace. To manage documents as folders, go to the Files."
         };
         myDashboards = DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), reqdOpts);
         if (Cast(myDashboards.contextMenuScripts, listSpec(ScriptField), null)?.length !== contextMenuScripts.length) {
@@ -523,19 +524,19 @@ export class CurrentUserUtils {
         
         const newFolder = `TreeView_addNewFolder()`;
         const newFolderOpts: DocumentOptions = {
-            _forceActive: true, _stayInCollection: true, _hideContextMenu: true, _width: 30, _height: 30,
+            _forceActive: true, _stayInCollection: true, _layout_hideContextMenu: true, _width: 30, _height: 30,
             title: "New folder", btnType: ButtonType.ClickButton, toolTip: "Create new folder", buttonText: "New folder", icon: "folder-plus", isSystem: true
         };
         const newFolderScript = { onClick: newFolder};
-        const newFolderButton = DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(myFilesystem?.buttonMenuDoc), newFolderOpts) ?? Docs.Create.FontIconDocument(newFolderOpts), newFolderScript);
+        const newFolderButton = DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(myFilesystem?.layout_headerButton), newFolderOpts) ?? Docs.Create.FontIconDocument(newFolderOpts), newFolderScript);
         
         const reqdOpts:DocumentOptions = { _layout_showTitle: "title", _height: 100, _gridGap: 5, _forceActive: true, _lockedPosition: true, 
-            title: "My Documents", buttonMenu: true, buttonMenuDoc: newFolderButton, treeViewHideTitle: true, targetDropAction: "proto", isSystem: true,
-            isFolder: true, treeViewType: TreeViewType.fileSystem, childHideLinkButton: true, boxShadow: "0 0", childDontRegisterViews: true, 
+            title: "My Documents", layout_headerButton: newFolderButton, treeViewHideTitle: true, targetDropAction: "proto", isSystem: true,
+            isFolder: true, treeViewType: TreeViewType.fileSystem, childHideLinkButton: true, layout_boxShadow: "0 0", childDontRegisterViews: true, 
             treeViewTruncateTitleWidth: 350, ignoreClick: true, childDropAction: "embed",
             childContextMenuLabels: new List<string>(["Create new folder"]),
             childContextMenuIcons: new List<string>(["plus"]),
-            explainer: "This is your file manager where you can create folders to keep track of documents independently of your dashboard."
+            layout_explainer: "This is your file manager where you can create folders to keep track of documents independently of your dashboard."
         };
         myFilesystem = DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, [myFileOrphans]);
         const childContextMenuScripts = [newFolder];
@@ -548,21 +549,21 @@ export class CurrentUserUtils {
     /// initializes the panel displaying docs that have been recently closed
     static setupRecentlyClosed(doc: Doc, field:string) {
         const reqdOpts:DocumentOptions = { _layout_showTitle: "title", _lockedPosition: true, _gridGap: 5, _forceActive: true, 
-            title: "My Recently Closed", buttonMenu: true, childHideLinkButton: true, treeViewHideTitle: true, childDropAction: "embed", isSystem: true,
-            treeViewTruncateTitleWidth: 350, ignoreClick: true,  boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same",
+            title: "My Recently Closed", childHideLinkButton: true, treeViewHideTitle: true, childDropAction: "embed", isSystem: true,
+            treeViewTruncateTitleWidth: 350, ignoreClick: true,  layout_boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same",
             contextMenuLabels: new List<string>(["Empty recently closed"]),
             contextMenuIcons:new List<string>(["trash"]),
-            explainer: "Recently closed documents appear in this menu. They will only be deleted if you explicity empty this list."
+            layout_explainer: "Recently closed documents appear in this menu. They will only be deleted if you explicity empty this list."
         };
         const recentlyClosed = DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), reqdOpts);
 
         const clearAll = (target:string) => `getProto(${target}).data = new List([])`;
-        const clearBtnsOpts:DocumentOptions = { _width: 30, _height: 30, _forceActive: true, _stayInCollection: true, _hideContextMenu: true, 
+        const clearBtnsOpts:DocumentOptions = { _width: 30, _height: 30, _forceActive: true, _stayInCollection: true, _layout_hideContextMenu: true, 
             title: "Empty", target: recentlyClosed, btnType: ButtonType.ClickButton,  buttonText: "Empty", icon: "trash", isSystem: true,
             toolTip: "Empty recently closed",};
-        const clearDocsButton = DocUtils.AssignDocField(recentlyClosed, "clearDocsBtn", (opts) => Docs.Create.FontIconDocument(opts), clearBtnsOpts, undefined, {onClick: clearAll("self.target")});
+        DocUtils.AssignDocField(recentlyClosed, "layout_headerButton", (opts) => Docs.Create.FontIconDocument(opts), clearBtnsOpts, undefined, {onClick: clearAll("self.target")});
 
-        if (recentlyClosed.buttonMenuDoc !== clearDocsButton) Doc.GetProto(recentlyClosed).buttonMenuDoc = clearDocsButton;
+        //if (recentlyClosed.layout_headerButton !== clearDocsButton) Doc.GetProto(recentlyClosed).layout_headerButton = clearDocsButton;
         
         if (!Cast(recentlyClosed.contextMenuScripts, listSpec(ScriptField),null)?.find((script) => script.script.originalScript === clearAll("self"))) {
             recentlyClosed.contextMenuScripts = new List<ScriptField>([ScriptField.MakeScript(clearAll("self"))!])
@@ -574,7 +575,7 @@ export class CurrentUserUtils {
     static setupUserDocView(doc: Doc, field:string) {
         const reqdOpts:DocumentOptions = {
             _lockedPosition: true, _gridGap: 5, _forceActive: true, title: Doc.CurrentUserEmail +"-view",
-            boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", ignoreClick: true, isSystem: true,
+            layout_boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", ignoreClick: true, isSystem: true,
             treeViewHideTitle: true, treeViewTruncateTitleWidth: 350
         };
         if (!doc[field]) DocUtils.AssignOpts(doc, {treeViewOpen: true, treeViewExpandedView: "fields" });
@@ -582,14 +583,14 @@ export class CurrentUserUtils {
     }
 
     static linearButtonList = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.LinearDocument(docs, {
-        ...opts, _gridGap: 0, _xMargin: 5, _yMargin: 5, boxShadow: "0 0", _forceActive: true,
+        ...opts, _gridGap: 0, _xMargin: 5, _yMargin: 5, layout_boxShadow: "0 0", _forceActive: true,
         dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }),
         _lockedPosition: true, isSystem: true, flexDirection: "row"
     })
 
     static createToolButton = (opts: DocumentOptions) => Docs.Create.FontIconDocument({
-        btnType: ButtonType.ToolButton, _forceActive: true, _hideContextMenu: true, 
-        _removeDropProperties: new List<string>([ "_hideContextMenu", "stayInCollection"]), 
+        btnType: ButtonType.ToolButton, _forceActive: true, _layout_hideContextMenu: true, 
+        _removeDropProperties: new List<string>([ "_layout_hideContextMenu", "stayInCollection"]), 
         _nativeWidth: 40, _nativeHeight: 40, _width: 40, _height: 40, isSystem: true, ...opts,
     })
 
@@ -609,8 +610,8 @@ export class CurrentUserUtils {
         ];
         const btns = btnDescs.map(desc => dockBtn({_width: 30, _height: 30, defaultDoubleClick: 'ignore', dontUndo: true, _stayInCollection: true, ...desc.opts}, desc.scripts));
         const dockBtnsReqdOpts:DocumentOptions = {
-            title: "docked buttons", _height: 40, flexGap: 0, boxShadow: "standard", childDropAction: 'embed',
-            childDontRegisterViews: true, linearViewIsExpanded: true, linearViewExpandable: true, ignoreClick: true
+            title: "docked buttons", _height: 40, flexGap: 0, layout_boxShadow: "standard", childDropAction: 'embed',
+            childDontRegisterViews: true, linearView_IsExpanded: 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 });
@@ -701,12 +702,12 @@ export class CurrentUserUtils {
             { title: "Back",    icon: "chevron-left",  toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton,         expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 20,  scripts: { onClick: 'prevKeyFrame(_readOnly_)'}},
             { title: "Num", icon:"",toolTip: "Frame Number (click to toggle edit mode)",btnType: ButtonType.TextButton,          expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}},
             { title: "Fwd",     icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton,         expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 20,  scripts: { onClick: 'nextKeyFrame(_readOnly_)'}},
-            { title: "Text",    icon: "Text",          toolTip: "Text functions",       subMenu: CurrentUserUtils.textTools(),   expertMode: false, toolType:DocumentType.RTF,            funcs: {                                                                             linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
-            { title: "Ink",     icon: "Ink",           toolTip: "Ink functions",        subMenu: CurrentUserUtils.inkTools(),    expertMode: false, toolType:DocumentType.INK,            funcs: {                                                                             linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available
-            { title: "Doc",     icon: "Doc",           toolTip: "Freeform Doc tools",   subMenu: CurrentUserUtils.freeTools(),   expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)`, linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
-            { title: "View",    icon: "View",          toolTip: "View tools",           subMenu: CurrentUserUtils.viewTools(),   expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
-            { title: "Web",     icon: "Web",           toolTip: "Web functions",        subMenu: CurrentUserUtils.webTools(),    expertMode: false, toolType:DocumentType.WEB,            funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Only when Web is selected
-            { title: "Schema",  icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",    subMenu: CurrentUserUtils.schemaTools(), expertMode: false, toolType:CollectionViewType.Schema,   funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} } // Only when Schema is selected
+            { title: "Text",    icon: "Text",          toolTip: "Text functions",       subMenu: CurrentUserUtils.textTools(),   expertMode: false, toolType:DocumentType.RTF,            funcs: {                                                                             linearView_IsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
+            { title: "Ink",     icon: "Ink",           toolTip: "Ink functions",        subMenu: CurrentUserUtils.inkTools(),    expertMode: false, toolType:DocumentType.INK,            funcs: {                                                                             linearView_IsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available
+            { title: "Doc",     icon: "Doc",           toolTip: "Freeform Doc tools",   subMenu: CurrentUserUtils.freeTools(),   expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)`, linearView_IsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
+            { title: "View",    icon: "View",          toolTip: "View tools",           subMenu: CurrentUserUtils.viewTools(),   expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
+            { title: "Web",     icon: "Web",           toolTip: "Web functions",        subMenu: CurrentUserUtils.webTools(),    expertMode: false, toolType:DocumentType.WEB,            funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Only when Web is selected
+            { title: "Schema",  icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",subMenu: CurrentUserUtils.schemaTools(), expertMode: false, toolType:CollectionViewType.Schema,   funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} } // Only when Schema is selected
         ];
     }
 
@@ -719,7 +720,7 @@ export class CurrentUserUtils {
             _nativeWidth: params.width ?? 30, _width: params.width ?? 30,
             _height: 30, _nativeHeight: 30, linearBtnWidth: params.linearBtnWidth,
             toolType: params.toolType, expertMode: params.expertMode,
-            _stayInCollection: true, _hideContextMenu: true, _lockedPosition: true,
+            _stayInCollection: true, _layout_hideContextMenu: true, _lockedPosition: true,
             _removeDropProperties: new List<string>([ "_stayInCollection"]),
         };
         const reqdFuncs:{[key:string]:any} = {
@@ -731,7 +732,7 @@ export class CurrentUserUtils {
 
     /// Initializes all the default buttons for the top bar context menu
     static setupContextMenuButtons(doc: Doc, field="myContextMenuBtns") {
-        const reqdCtxtOpts:DocumentOptions = { title: "context menu buttons", dontUndo:true, flexGap: 0, childDropAction: 'embed', childDontRegisterViews: true,  linearViewIsExpanded: true, ignoreClick: true, linearViewExpandable: false, _height: 35 };
+        const reqdCtxtOpts:DocumentOptions = { title: "context menu buttons", dontUndo:true, flexGap: 0, childDropAction: 'embed', childDontRegisterViews: true,  linearView_IsExpanded: 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 => {
             const menuBtnDoc = DocListCast(ctxtMenuBtnsDoc?.data).find(doc => doc.title === params.title);
@@ -740,7 +741,7 @@ export class CurrentUserUtils {
             } else {
                 const reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, dontUndo: true,
                     childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: params.scripts?.onClick ? false : true,
-                    linearViewSubMenu: true, linearViewExpandable: true, };
+                    linearView_SubMenu: true, linearView_Expandable: true, };
                 const items = params.subMenu?.map(sub =>
                     this.setupContextMenuButton(sub, DocListCast(menuBtnDoc?.data).find(doc => doc.title === sub.title))
                 );
@@ -793,8 +794,8 @@ export class CurrentUserUtils {
             "acl-Public": SharingPermissions.Augment, "_acl-Public": SharingPermissions.Augment,
             childDropAction: "embed", isSystem: true, contentPointerEvents: "all", childLimitHeight: 0, _yMargin: 0, _gridGap: 15, childDontRegisterViews:true,
             // NOTE: treeViewHideTitle & _layout_showTitle is for a TreeView's editable title, _layout_showTitle is for DocumentViews title bar
-            _layout_showTitle: "title", treeViewHideTitle: true, ignoreClick: true, _lockedPosition: true, boxShadow: "0 0", _chromeHidden: true, dontRegisterView: true, 
-            explainer: "This is where documents or dashboards that other users have shared with you will appear. To share a document or dashboard right click and select 'Share'"
+            _layout_showTitle: "title", treeViewHideTitle: true, ignoreClick: true, _lockedPosition: true, layout_boxShadow: "0 0", _chromeHidden: true, dontRegisterView: true, 
+            layout_explainer: "This is where documents or dashboards that other users have shared with you will appear. To share a document or dashboard right click and select 'Share'"
         };
         
         DocUtils.AssignDocField(doc, "mySharedDocs", opts => Docs.Create.TreeDocument([], opts, sharingDocumentId + "layout", sharingDocumentId), sharedDocOpts, undefined, sharedScripts);
@@ -803,17 +804,17 @@ export class CurrentUserUtils {
     /// Import option on the left side button panel 
     static setupImportSidebar(doc: Doc, field:string) {   
         const reqdOpts:DocumentOptions = {
-            title: "My Imports", _forceActive: true, buttonMenu: true, ignoreClick: true, _layout_showTitle: "title", 
-            _stayInCollection: true, _hideContextMenu: true, childLimitHeight: 0,
-            childDropAction: "copy", _layout_autoHeight: true, _yMargin: 50, _gridGap: 15, boxShadow: "0 0", _lockedPosition: true, isSystem: true, _chromeHidden: true,
-            dontRegisterView: true, explainer: "This is where documents that are Imported into Dash will go."
+            title: "My Imports", _forceActive: true, ignoreClick: true, _layout_showTitle: "title", 
+            _stayInCollection: true, _layout_hideContextMenu: true, childLimitHeight: 0,
+            childDropAction: "copy", _layout_autoHeight: true, _yMargin: 50, _gridGap: 15, layout_boxShadow: "0 0", _lockedPosition: true, isSystem: true, _chromeHidden: true,
+            dontRegisterView: true, layout_explainer: "This is where documents that are Imported into Dash will go."
         };
         const myImports = DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.StackingDocument([], opts), reqdOpts);
     
         const reqdBtnOpts:DocumentOptions = { _forceActive: true, toolTip: "Import from computer",
-            _width: 30, _height: 30, _stayInCollection: true, _hideContextMenu: true, title: "Import", btnType: ButtonType.ClickButton, 
+            _width: 30, _height: 30, _stayInCollection: true, _layout_hideContextMenu: true, title: "Import", btnType: ButtonType.ClickButton, 
             buttonText: "Import", icon: "upload", isSystem: true };
-        DocUtils.AssignDocField(myImports, "buttonMenuDoc", (opts) => Docs.Create.FontIconDocument(opts), reqdBtnOpts, undefined, { onClick: "importDocument()" });
+        DocUtils.AssignDocField(myImports, "layout_headerButton", (opts) => Docs.Create.FontIconDocument(opts), reqdBtnOpts, undefined, { onClick: "importDocument()" });
         return myImports;
     }
     /// Updates the UserDoc to have all required fields, docs, etc.  No changes should need to be
@@ -850,7 +851,7 @@ export class CurrentUserUtils {
         doc.defaultAclPrivate ?? (doc.defaultAclPrivate = false);
         doc.savedFilters ?? (doc.savedFilters = new List<Doc>());
         doc.filterDocCount = 0;
-        doc.freezeChildren = "remove|add";
+        doc.treeViewFreezeChildren = "remove|add";
         this.setupLinkDocs(doc, linkDatabaseId);
         this.setupSharedDocs(doc, sharingDocumentId);  // sets up the right sidebar collection for mobile upload documents and sharing
         this.setupDefaultIconTemplates(doc);  // creates a set of icon templates triggered by the document deoration icon
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 7a88ca991..612f16ce9 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -189,7 +189,7 @@ export class DocumentManager {
         while (
             containerDocContext.length &&
             containerDocContext[0]?.embedContainer &&
-            DocCast(containerDocContext[0].embedContainer)?.type_collection !== CollectionViewType.Docking &&
+            DocCast(containerDocContext[0].embedContainer)?._type_collection !== CollectionViewType.Docking &&
             (includeExistingViews || !DocumentManager.Instance.getDocumentView(containerDocContext[0]))
         ) {
             containerDocContext = [Cast(containerDocContext[0].embedContainer, Doc, null), ...containerDocContext];
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index fb4a8985c..668de5408 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -517,7 +517,7 @@ export namespace DragManager {
 
             const target = document.elementFromPoint(e.x, e.y);
 
-            if (target && !Doc.UserDoc()._noAutoscroll && !options?.noAutoscroll && !dragData.draggedDocuments?.some((d: any) => d._noAutoscroll)) {
+            if (target && !Doc.UserDoc()._noAutoscroll && !options?.noAutoscroll && !dragData.draggedDocuments?.some((d: any) => d._freeform_noAutoPan)) {
                 const autoScrollHandler = () => {
                     target.dispatchEvent(
                         new CustomEvent<React.DragEvent>('dashDragAutoScroll', {
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 41c2fa889..dbb05917e 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -134,7 +134,7 @@ export class LinkManager {
     public createlink_relationshipLists = () => {
         //create new lists for link relations and their associated colors if the lists don't already exist
         !Doc.UserDoc().link_relationshipList && (Doc.UserDoc().link_relationshipList = new List<string>());
-        !Doc.UserDoc().linkColorList && (Doc.UserDoc().linkColorList = new List<string>());
+        !Doc.UserDoc().link_ColorList && (Doc.UserDoc().link_ColorList = new List<string>());
         !Doc.UserDoc().link_relationshipSizes && (Doc.UserDoc().link_relationshipSizes = new List<number>());
     };
 
diff --git a/src/client/util/TrackMovements.ts b/src/client/util/TrackMovements.ts
index f83b6af0e..0e56ee1bc 100644
--- a/src/client/util/TrackMovements.ts
+++ b/src/client/util/TrackMovements.ts
@@ -3,6 +3,7 @@ import { NumCast } from '../../fields/Types';
 import { Doc, DocListCast } from '../../fields/Doc';
 import { CollectionDockingView } from '../views/collections/CollectionDockingView';
 import { Id } from '../../fields/FieldSymbols';
+import { CollectionViewType } from '../documents/DocumentTypes';
 
 export type Movement = {
     time: number;
@@ -89,7 +90,7 @@ export class TrackMovements {
         if (this.recordingFFViews === null) return;
 
         // so that the size comparisons are correct, we must filter to only the FFViews
-        const isFFView = (doc: Doc) => doc && 'type_collection' in doc && doc.type_collection === 'freeform';
+        const isFFView = (doc: Doc) => doc && doc._type_collection === CollectionViewType.Freeform;
         const tabbedFFViews = new Set<Doc>();
         for (const DashDoc of tabbedDocs) {
             if (isFFView(DashDoc)) tabbedFFViews.add(DashDoc);
diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx
index 94dd010c3..50cf2226e 100644
--- a/src/client/views/DashboardView.tsx
+++ b/src/client/views/DashboardView.tsx
@@ -390,7 +390,7 @@ export class DashboardView extends React.Component {
             _width: 30,
             _height: 30,
             _stayInCollection: true,
-            _hideContextMenu: true,
+            _layout_hideContextMenu: true,
             title: 'New trail',
             toolTip: 'Create new trail',
             btnType: ButtonType.ClickButton,
@@ -415,16 +415,15 @@ export class DashboardView extends React.Component {
             childDropAction: 'embed',
             treeViewTruncateTitleWidth: 150,
             ignoreClick: true,
-            buttonMenu: true,
-            buttonMenuDoc: myTrailsBtn,
+            layout_headerButton: myTrailsBtn,
             contextMenuIcons: new List<string>(['plus']),
             contextMenuLabels: new List<string>(['Create New Trail']),
             _lockedPosition: true,
-            boxShadow: '0 0',
+            layout_boxShadow: '0 0',
             childDontRegisterViews: true,
             targetDropAction: 'same',
             isSystem: true,
-            explainer: 'All of the trails that you have created will appear here.',
+            layout_explainer: 'All of the trails that you have created will appear here.',
         };
         const myTrails = DocUtils.AssignScripts(Docs.Create.TreeDocument([], reqdOpts), { treeViewChildDoubleClick: 'openPresentation(documentView.rootDoc)' });
         dashboardDoc.myTrails = new PrefetchProxy(myTrails);
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 6fab4430d..db24229dc 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -128,8 +128,6 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
 
         isAnyChildContentActive = () => this._isAnyChildContentActive;
 
-        lookupField = (field: string) => ScriptCast((this.layoutDoc as any).lookupField)?.script.run({ self: this.layoutDoc, data: this.rootDoc, field: field }).result;
-
         protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
 
         @computed public get annotationKey() {
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 547d844ca..35b0b22a8 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -589,7 +589,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                         <LinkPopup
                             key="popup"
                             showPopup={this._showLinkPopup}
-                            linkCreated={link => (link.layout_linkDisplay = !IsFollowLinkScript(this.props.views().lastElement()?.rootDoc.onClick))}
+                            linkCreated={link => (link.link_displayLine = !IsFollowLinkScript(this.props.views().lastElement()?.rootDoc.onClick))}
                             linkCreateAnchor={() => this.props.views().lastElement()?.ComponentView?.getAnchor?.(true)}
                             linkFrom={() => this.props.views().lastElement()?.rootDoc}
                         />
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index e4d50b143..528b82e3e 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -332,7 +332,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                         const docMax = Math.min(NumCast(doc.width) / 2, NumCast(doc.height) / 2);
                         const ratio = dist / maxDist;
                         const radius = Math.min(1, ratio) * docMax;
-                        doc.borderRounding = `${radius}px`;
+                        doc.layout_borderRounding = `${radius}px`;
                     });
                 return false;
             }, // moveEvent
@@ -588,7 +588,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                     let dW = docwidth * (dWin / refWidth);
                     let dH = docheight * (dHin / refHeight);
                     const scale = docView.props.ScreenToLocalTransform().Scale;
-                    const modifyNativeDim = (e.ctrlKey || doc.layout_forceReflow) && doc.nativeDimModifiable && ((!dragBottom && !dragTop) || e.ctrlKey || doc.nativeHeightUnfrozen);
+                    const modifyNativeDim = (e.ctrlKey && doc.nativeDimModifiable) || (doc.layout_forceReflow && !dragBottom && !dragTop) || (doc.nativeHeightUnfrozen && (dragBottom || dragTop || e.ctrlKey));
                     if (nwidth && nheight) {
                         if (nwidth / nheight !== docwidth / docheight && !dragBottom && !dragTop) {
                             docheight = (nheight / nwidth) * docwidth;
@@ -636,7 +636,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                                 }
                             }
                             if (!modifyNativeDim) {
-                                actualdH = Math.min((nheight / nwidth) * docwidth, actualdH);
+                                actualdH = (nheight / nwidth) * docwidth; //, actualdH);
                             }
                             doc._height = actualdH;
                         }
@@ -750,13 +750,13 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         const hideDecorations = seldocview.props.hideDecorations || seldocview.rootDoc.hideDecorations;
         const hideResizers = hideDecorations || seldocview.props.hideResizeHandles || seldocview.rootDoc.layout_hideResizeHandles || this._isRounding || this._isRotating;
         const hideTitle = hideDecorations || seldocview.props.hideDecorationTitle || seldocview.rootDoc.layout_hideDecorationTitle || this._isRounding || this._isRotating;
-        const hideDocumentButtonBar = hideDecorations || seldocview.props.hideDocumentButtonBar || seldocview.rootDoc.hideDocumentButtonBar || this._isRounding || this._isRotating;
+        const hideDocumentButtonBar = hideDecorations || seldocview.props.hideDocumentButtonBar || seldocview.rootDoc.layout_hideDocumentButtonBar || this._isRounding || this._isRotating;
         // if multiple documents have been opened at the same time, then don't show open button
         const hideOpenButton =
             hideDecorations ||
             seldocview.props.hideOpenButton ||
-            seldocview.rootDoc.hideOpenButton ||
-            SelectionManager.Views().some(docView => docView.props.Document._stayInCollection || docView.props.Document.isGroup || docView.props.Document.hideOpenButton) ||
+            seldocview.rootDoc.layout_hideOpenButton ||
+            SelectionManager.Views().some(docView => docView.rootDoc._stayInCollection || docView.rootDoc.isGroup || docView.rootDoc.layout_hideOpenButton) ||
             this._isRounding ||
             this._isRotating;
         const hideDeleteButton =
@@ -767,7 +767,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
             seldocview.rootDoc.hideDeleteButton ||
             SelectionManager.Views().some(docView => {
                 const collectionAcl = docView.props.docViewPath()?.lastElement() ? GetEffectiveAcl(docView.props.docViewPath().lastElement().rootDoc[DocData]) : AclEdit;
-                return (docView.rootDoc.stayInCollection && !docView.rootDoc.timelineLabel) || (collectionAcl !== AclAdmin && collectionAcl !== AclEdit && GetEffectiveAcl(docView.rootDoc) !== AclAdmin);
+                return (docView.rootDoc.stayInCollection && !docView.rootDoc._isTimelineLabel) || (collectionAcl !== AclAdmin && collectionAcl !== AclEdit && GetEffectiveAcl(docView.rootDoc) !== AclAdmin);
             });
 
         const topBtn = (key: string, icon: string, pointerDown: undefined | ((e: React.PointerEvent) => void), click: undefined | ((e: any) => void), title: string) => (
@@ -796,7 +796,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
 
         // Radius constants
         const useRounding = seldocview.ComponentView instanceof ImageBox || seldocview.ComponentView instanceof FormattedTextBox || seldocview.ComponentView instanceof CollectionFreeFormView;
-        const borderRadius = numberValue(StrCast(seldocview.rootDoc.borderRounding));
+        const borderRadius = numberValue(StrCast(seldocview.rootDoc.layout_borderRounding));
         const docMax = Math.min(NumCast(seldocview.rootDoc.width) / 2, NumCast(seldocview.rootDoc.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;
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index 53c1f1018..fe42628cd 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -64,7 +64,7 @@ export class FilterPanel extends React.Component<filterProps> {
      * The current attributes selected to filter based on
      */
     @computed get activeFilters() {
-        return StrListCast(this.targetDoc?._docFilters);
+        return StrListCast(this.targetDoc?._childFilters);
     }
 
     /**
@@ -108,8 +108,8 @@ export class FilterPanel extends React.Component<filterProps> {
     @observable _chosenFacets = new ObservableMap<string, 'text' | 'checkbox' | 'slider' | 'range'>();
     @computed get activeFacets() {
         const facets = new Map<string, 'text' | 'checkbox' | 'slider' | 'range'>(this._chosenFacets);
-        StrListCast(this.targetDoc?._docFilters).map(filter => facets.set(filter.split(':')[0], filter.split(':')[2] === 'match' ? 'text' : 'checkbox'));
-        setTimeout(() => StrListCast(this.targetDoc?._docFilters).map(action(filter => this._chosenFacets.set(filter.split(':')[0], filter.split(':')[2] === 'match' ? 'text' : 'checkbox'))));
+        StrListCast(this.targetDoc?._childFilters).map(filter => facets.set(filter.split(':')[0], filter.split(':')[2] === 'match' ? 'text' : 'checkbox'));
+        setTimeout(() => StrListCast(this.targetDoc?._childFilters).map(action(filter => this._chosenFacets.set(filter.split(':')[0], filter.split(':')[2] === 'match' ? 'text' : 'checkbox'))));
         return facets;
     }
     /**
@@ -174,7 +174,7 @@ export class FilterPanel extends React.Component<filterProps> {
                         <Select placeholder="Add a filter..." options={options} isMulti={false} onChange={val => this.facetClick((val as UserOptions).value)} onKeyDown={e => e.stopPropagation()} value={null} closeMenuOnSelect={true} />
                     </div>
                     <div className="filterBox-select-bool">
-                        <select className="filterBox-selection" onChange={action(e => this.targetDoc && (this.targetDoc._filterBoolean = (e.target as any).value))} defaultValue={StrCast(this.targetDoc?.filterBoolean)}>
+                        <select className="filterBox-selection" onChange={action(e => this.targetDoc && (this.targetDoc._childFilters_boolean = (e.target as any).value))} defaultValue={StrCast(this.targetDoc?.childFilters_boolean)}>
                             {['AND', 'OR'].map(bool => (
                                 <option value={bool} key={bool}>
                                     {bool}
@@ -202,7 +202,7 @@ export class FilterPanel extends React.Component<filterProps> {
                 return (
                     <input
                         placeholder={
-                            StrListCast(this.targetDoc._docFilters)
+                            StrListCast(this.targetDoc._childFilters)
                                 .find(filter => filter.split(':')[0] === facetHeader)
                                 ?.split(':')[1] ?? '-empty-'
                         }
@@ -218,7 +218,7 @@ export class FilterPanel extends React.Component<filterProps> {
                             <input
                                 style={{ width: 20, marginLeft: 20 }}
                                 checked={
-                                    StrListCast(this.targetDoc._docFilters)
+                                    StrListCast(this.targetDoc._childFilters)
                                         .find(filter => filter.split(':')[0] === facetHeader && filter.split(':')[1] == facetValue)
                                         ?.split(':')[2] === 'check'
                                 }
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 141e99c66..0951bff22 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -140,7 +140,7 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
                 title: 'buttons',
                 _layout_autoHeight: true,
                 _yMargin: 5,
-                linearViewIsExpanded: true,
+                linearView_IsExpanded: true,
                 backgroundColor: 'white',
                 isSystem: true,
             });
@@ -148,7 +148,7 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
                 _width: 300,
                 _height: 25,
                 _layout_autoHeight: true,
-                linearViewIsExpanded: true,
+                linearView_IsExpanded: true,
                 flexDirection: 'column',
                 isSystem: true,
             });
@@ -1013,8 +1013,8 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
                 focus={emptyFunction}
                 whenChildContentsActiveChanged={emptyFunction}
                 bringToFront={emptyFunction}
-                docRangeFilters={returnEmptyFilter}
-                docFilters={returnEmptyFilter}
+                childFiltersByRanges={returnEmptyFilter}
+                childFilters={returnEmptyFilter}
                 searchFilterDocs={returnEmptyDoclist}
             />
         );
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 1b309a6be..286d39943 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -43,7 +43,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
     @observable private static _layoutTemplateString: Opt<string>;
     @observable private static _doc: Opt<Doc>;
     @observable private static _docTarget: Opt<Doc>;
-    @observable private static _docFilters: string[] = []; // filters
+    @observable private static _childFilters: string[] = []; // filters
     private static _savedState: Opt<LightboxSavedState>;
     private static _history: Opt<{ doc: Doc; target?: Doc }[]> = [];
     @observable private static _future: Opt<Doc[]> = [];
@@ -58,7 +58,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
             this.LightboxDoc.layout_fieldKey = this._savedState.layout_fieldKey;
         }
         if (!doc) {
-            this._docFilters && (this._docFilters.length = 0);
+            this._childFilters && (this._childFilters.length = 0);
             this._future = this._history = [];
             Doc.ActiveTool = InkTool.None;
             MainView.Instance._exploreMode = false;
@@ -134,7 +134,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
     @action
     public static SetCookie(cookie: string) {
         if (this.LightboxDoc && cookie) {
-            this._docFilters = (f => (this._docFilters ? [this._docFilters.push(f) as any, this._docFilters][1] : [f]))(`cookies:${cookie}:provide`);
+            this._childFilters = (f => (this._childFilters ? [this._childFilters.push(f) as any, this._childFilters][1] : [f]))(`cookies:${cookie}:provide`);
         }
     }
     public static AddDocTab = (doc: Doc, location: OpenWhere, layoutTemplate?: Doc | string) => {
@@ -148,7 +148,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
             layoutTemplate
         );
     };
-    docFilters = () => LightboxView._docFilters || [];
+    childFilters = () => LightboxView._childFilters || [];
     addDocTab = LightboxView.AddDocTab;
     @action public static Next() {
         const doc = LightboxView._doc!;
@@ -261,8 +261,8 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                             renderDepth={0}
                             rootSelected={returnTrue}
                             docViewPath={returnEmptyDoclist}
-                            docFilters={this.docFilters}
-                            docRangeFilters={returnEmptyFilter}
+                            childFilters={this.childFilters}
+                            childFiltersByRanges={returnEmptyFilter}
                             searchFilterDocs={returnEmptyDoclist}
                             addDocument={undefined}
                             removeDocument={undefined}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 8e112dddb..b0e992cb6 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -608,8 +608,8 @@ export class MainView extends React.Component {
                     focus={emptyFunction}
                     whenChildContentsActiveChanged={emptyFunction}
                     bringToFront={emptyFunction}
-                    docFilters={returnEmptyFilter}
-                    docRangeFilters={returnEmptyFilter}
+                    childFilters={returnEmptyFilter}
+                    childFiltersByRanges={returnEmptyFilter}
                     searchFilterDocs={returnEmptyDoclist}
                 />
             </div>
@@ -637,8 +637,8 @@ export class MainView extends React.Component {
                     focus={emptyFunction}
                     whenChildContentsActiveChanged={emptyFunction}
                     bringToFront={emptyFunction}
-                    docFilters={returnEmptyFilter}
-                    docRangeFilters={returnEmptyFilter}
+                    childFilters={returnEmptyFilter}
+                    childFiltersByRanges={returnEmptyFilter}
                     searchFilterDocs={returnEmptyDoclist}
                     suppressSetHeight={true}
                     renderDepth={this._hideUI ? 0 : -1}
@@ -736,8 +736,8 @@ export class MainView extends React.Component {
                         focus={emptyFunction}
                         whenChildContentsActiveChanged={emptyFunction}
                         bringToFront={emptyFunction}
-                        docFilters={returnEmptyFilter}
-                        docRangeFilters={returnEmptyFilter}
+                        childFilters={returnEmptyFilter}
+                        childFiltersByRanges={returnEmptyFilter}
                         searchFilterDocs={returnEmptyDoclist}
                     />
                 </div>
@@ -767,8 +767,8 @@ export class MainView extends React.Component {
                     isContentActive={returnTrue}
                     whenChildContentsActiveChanged={emptyFunction}
                     bringToFront={emptyFunction}
-                    docFilters={returnEmptyFilter}
-                    docRangeFilters={returnEmptyFilter}
+                    childFilters={returnEmptyFilter}
+                    childFiltersByRanges={returnEmptyFilter}
                     searchFilterDocs={returnEmptyDoclist}
                     scriptContext={this}
                 />
@@ -903,8 +903,8 @@ export class MainView extends React.Component {
                     renderDepth={0}
                     focus={emptyFunction}
                     whenChildContentsActiveChanged={emptyFunction}
-                    docFilters={returnEmptyFilter}
-                    docRangeFilters={returnEmptyFilter}
+                    childFilters={returnEmptyFilter}
+                    childFiltersByRanges={returnEmptyFilter}
                     searchFilterDocs={returnEmptyDoclist}
                 />
                 {['watching', 'recording'].includes(String(this.userDoc?.presentationMode) ?? '') ? <div style={{ border: '.5rem solid green', padding: '5px' }}>{StrCast(this.userDoc?.presentationMode)}</div> : <></>}
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 6c36d39b9..a4a2c1df9 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -126,7 +126,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
                           if (!e.aborted && e.linkDocument) {
                               Doc.GetProto(e.linkDocument).link_relationship = 'cropped image';
                               Doc.GetProto(e.linkDocument).title = 'crop: ' + this.props.docView.rootDoc.title;
-                              Doc.GetProto(e.linkDocument).layout_linkDisplay = false;
+                              Doc.GetProto(e.linkDocument).link_displayLine = false;
                           }
                       },
                   });
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 59865cba3..339507f65 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -228,8 +228,8 @@ export class OverlayView extends React.Component {
                             docViewPath={returnEmptyDoclist}
                             addDocTab={DocumentViewInternal.addDocTabFunc}
                             pinToPres={emptyFunction}
-                            docFilters={returnEmptyFilter}
-                            docRangeFilters={returnEmptyFilter}
+                            childFilters={returnEmptyFilter}
+                            childFiltersByRanges={returnEmptyFilter}
                             searchFilterDocs={returnEmptyDoclist}
                         />
                     </div>
diff --git a/src/client/views/Palette.tsx b/src/client/views/Palette.tsx
index 3ad28c418..749eb08a2 100644
--- a/src/client/views/Palette.tsx
+++ b/src/client/views/Palette.tsx
@@ -56,8 +56,8 @@ export default class Palette extends React.Component<PaletteProps> {
                             styleProvider={returnEmptyString}
                             whenChildContentsActiveChanged={emptyFunction}
                             bringToFront={emptyFunction}
-                            docFilters={returnEmptyFilter}
-                            docRangeFilters={returnEmptyFilter}
+                            childFilters={returnEmptyFilter}
+                            childFiltersByRanges={returnEmptyFilter}
                             searchFilterDocs={returnEmptyDoclist}
                         />
                         <div className="palette-cover" style={{ transform: `translate(${Math.max(0, this._selectedIndex) * 50.75 + 23}px, 0px)` }}></div>
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 76828a576..11b89fd69 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -133,14 +133,14 @@ export class PropertiesButtons extends React.Component<{}, {}> {
                     const containerDoc = dv.rootDoc;
                     //containerDoc.followAllLinks =
                     // containerDoc.noShadow =
-                    // containerDoc.disableDocBrushing =
+                    // containerDoc.layout_disableBrushing =
                     // containerDoc._forceActive =
                     //containerDoc._freeform_fitContentsToBox =
                     containerDoc._isLightbox = !containerDoc._isLightbox;
                     //containerDoc._xPadding = containerDoc._yPadding = containerDoc._isLightbox ? 10 : undefined;
                     const containerContents = DocListCast(dv.dataDoc[dv.props.fieldKey ?? Doc.LayoutFieldKey(containerDoc)]);
                     //dv.rootDoc.onClick = ScriptField.MakeScript('{self.data = undefined; documentView.select(false)}', { documentView: 'any' });
-                    containerContents.forEach(doc => LinkManager.Links(doc).forEach(link => (link.layout_linkDisplay = false)));
+                    containerContents.forEach(doc => LinkManager.Links(doc).forEach(link => (link.link_displayLine = false)));
                 });
             }
         );
diff --git a/src/client/views/PropertiesDocBacklinksSelector.tsx b/src/client/views/PropertiesDocBacklinksSelector.tsx
index 7b21629da..3e69bcba6 100644
--- a/src/client/views/PropertiesDocBacklinksSelector.tsx
+++ b/src/client/views/PropertiesDocBacklinksSelector.tsx
@@ -19,8 +19,8 @@ type PropertiesDocBacklinksSelectorProps = {
 @observer
 export class PropertiesDocBacklinksSelector extends React.Component<PropertiesDocBacklinksSelectorProps> {
     getOnClick = (link: Doc) => {
-        const linkSource = this.props.Document;
-        const other = LinkManager.getOppositeAnchor(link, linkSource);
+        const linkAnchor = this.props.Document;
+        const other = LinkManager.getOppositeAnchor(link, linkAnchor);
         const otherdoc = !other ? undefined : other.annotationOn && other.type !== DocumentType.RTF ? Cast(other.annotationOn, Doc, null) : other;
 
         if (otherdoc) {
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 00e077f96..1f2e21dd5 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -276,8 +276,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                         PanelHeight={panelHeight}
                         focus={emptyFunction}
                         ScreenToLocalTransform={this.getTransform}
-                        docFilters={returnEmptyFilter}
-                        docRangeFilters={returnEmptyFilter}
+                        childFilters={returnEmptyFilter}
+                        childFiltersByRanges={returnEmptyFilter}
                         searchFilterDocs={returnEmptyDoclist}
                         addDocument={returnFalse}
                         moveDocument={undefined}
@@ -941,26 +941,26 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     }
 
     /**
-     * Updates this.filterDoc's currentFilter and saves the docFilters on the currentFilter
+     * Updates this.filterDoc's currentFilter and saves the childFilters on the currentFilter
      */
     updateFilterDoc = (doc: Doc) => {
         if (this.selectedDoc) {
             if (doc === this.selectedDoc.currentFilter) return; // causes problems if you try to reapply the same doc
-            const savedDocFilters = doc._docFiltersList;
-            const currentDocFilters = this.selectedDoc._docFilters;
-            this.selectedDoc._docFilters = new List<string>();
-            (this.selectedDoc.currentFilter as Doc)._docFiltersList = currentDocFilters;
+            const savedDocFilters = doc._childFiltersList;
+            const currentDocFilters = this.selectedDoc._childFilters;
+            this.selectedDoc._childFilters = new List<string>();
+            (this.selectedDoc.currentFilter as Doc)._childFiltersList = currentDocFilters;
             this.selectedDoc.currentFilter = doc;
-            doc._docFiltersList = new List<string>();
-            this.selectedDoc._docFilters = savedDocFilters;
+            doc._childFiltersList = new List<string>();
+            this.selectedDoc._childFilters = savedDocFilters;
 
-            const savedDocRangeFilters = doc._docRangeFiltersList;
-            const currentDocRangeFilters = this.selectedDoc._docRangeFilters;
-            this.selectedDoc._docRangeFilters = new List<string>();
-            (this.selectedDoc.currentFilter as Doc)._docRangeFiltersList = currentDocRangeFilters;
+            const savedDocRangeFilters = doc._childFiltersByRangesList;
+            const currentDocRangeFilters = this.selectedDoc._childFiltersByRanges;
+            this.selectedDoc._childFiltersByRanges = new List<string>();
+            (this.selectedDoc.currentFilter as Doc)._childFiltersByRangesList = currentDocRangeFilters;
             this.selectedDoc.currentFilter = doc;
-            doc._docRangeFiltersList = new List<string>();
-            this.selectedDoc._docRangeFilters = savedDocRangeFilters;
+            doc._childFiltersByRangesList = new List<string>();
+            this.selectedDoc._childFiltersByRanges = savedDocRangeFilters;
         }
     };
 
@@ -1117,7 +1117,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
             Doc.GetProto(LinkManager.currentLink).link_relationship = value;
             const linkRelationshipList = StrListCast(Doc.UserDoc().link_relationshipList);
             const linkRelationshipSizes = NumListCast(Doc.UserDoc().link_relationshipSizes);
-            const linkColorList = StrListCast(Doc.UserDoc().linkColorList);
+            const linkColorList = StrListCast(Doc.UserDoc().link_ColorList);
 
             // if the relationship does not exist in the list, add it and a corresponding unique randomly generated color
             if (!linkRelationshipList?.includes(value)) {
@@ -1328,8 +1328,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                                     <div className="propertiesView-input inline">
                                         <p>Show link</p>
                                         <button
-                                            style={{ background: !LinkManager.currentLink?.layout_linkDisplay ? '' : '#4476f7', borderRadius: 3 }}
-                                            onPointerDown={e => this.toggleLinkProp(e, 'layout_linkDisplay')}
+                                            style={{ background: !LinkManager.currentLink?.link_displayLine ? '' : '#4476f7', borderRadius: 3 }}
+                                            onPointerDown={e => this.toggleLinkProp(e, 'link_displayLine')}
                                             onClick={e => e.stopPropagation()}
                                             className="propertiesButton">
                                             <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" />
@@ -1338,8 +1338,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                                     <div className="propertiesView-input inline" style={{ marginLeft: 10 }}>
                                         <p>Auto-move anchors</p>
                                         <button
-                                            style={{ background: !LinkManager.currentLink?.layout_autoMoveAnchors ? '' : '#4476f7', borderRadius: 3 }}
-                                            onPointerDown={e => this.toggleLinkProp(e, 'layout_autoMoveAnchors')}
+                                            style={{ background: !LinkManager.currentLink?.link_autoMoveAnchors ? '' : '#4476f7', borderRadius: 3 }}
+                                            onPointerDown={e => this.toggleLinkProp(e, 'link_autoMoveAnchors')}
                                             onClick={e => e.stopPropagation()}
                                             className="propertiesButton">
                                             <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
@@ -1348,8 +1348,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                                     <div className="propertiesView-input inline" style={{ marginLeft: 10 }}>
                                         <p>Display arrow</p>
                                         <button
-                                            style={{ background: !LinkManager.currentLink?.layout_linkDisplayArrow ? '' : '#4476f7', borderRadius: 3 }}
-                                            onPointerDown={e => this.toggleLinkProp(e, 'layout_linkDisplayArrow')}
+                                            style={{ background: !LinkManager.currentLink?.link_displayArrow ? '' : '#4476f7', borderRadius: 3 }}
+                                            onPointerDown={e => this.toggleLinkProp(e, 'link_displayArrow')}
                                             onClick={e => e.stopPropagation()}
                                             className="propertiesButton">
                                             <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" />
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 498e2ccba..db273cc88 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -63,11 +63,11 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
         return Array.from(keys.keys()).sort();
     }
     get filtersKey() {
-        return '_' + this.sidebarKey + '_docFilters';
+        return '_' + this.sidebarKey + '_childFilters';
     }
 
     anchorMenuClick = (anchor: Doc) => {
-        const startup = StrListCast(this.props.rootDoc.docFilters)
+        const startup = StrListCast(this.props.rootDoc.childFilters)
             .map(filter => filter.split(':')[0])
             .join(' ');
         const target = Docs.Create.TextDocument(startup, {
@@ -83,9 +83,9 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
         FormattedTextBox.SelectOnLoad = target[Id];
         FormattedTextBox.DontSelectInitialText = true;
         const link = DocUtils.MakeLink(anchor, target, { link_relationship: 'inline comment:comment on' });
-        link && (link.layout_linkDisplay = false);
+        link && (link.link_displayLine = false);
 
-        const taggedContent = this.docFilters()
+        const taggedContent = this.childFilters()
             .filter(data => data.split(':')[0])
             .map(data => {
                 const key = data.split(':')[0];
@@ -177,7 +177,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
     addDocument = (doc: Doc | Doc[]) => this.props.sidebarAddDocument(doc, this.sidebarKey);
     moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.props.moveDocument(doc, targetCollection, addDocument, this.sidebarKey);
     removeDocument = (doc: Doc | Doc[]) => this.props.removeDocument(doc, this.sidebarKey);
-    docFilters = () => [...StrListCast(this.props.layoutDoc._docFilters), ...StrListCast(this.props.layoutDoc[this.filtersKey])];
+    childFilters = () => [...StrListCast(this.props.layoutDoc._childFilters), ...StrListCast(this.props.layoutDoc[this.filtersKey])];
     layout_showTitle = () => 'title';
     setHeightCallback = (height: number) => this.props.setHeight?.(height + this.filtersHeight());
     sortByLinkAnchorY = (a: Doc, b: Doc) => {
@@ -238,7 +238,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
                         ref={this._stackRef}
                         PanelHeight={this.panelHeight}
                         PanelWidth={this.panelWidth}
-                        docFilters={this.docFilters}
+                        childFilters={this.childFilters}
                         sortFunc={this.sortByLinkAnchorY}
                         setHeight={this.setHeightCallback}
                         isAnnotationOverlay={false}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index cf0177f4b..9199bf931 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -115,7 +115,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
             allSorts[TreeSort.None] = { color: 'darkgray', label: '\u00A0\u00A0\u00A0' };
             return allSorts;
         case StyleProp.Highlighting:
-            if (doc && !doc.disableDocBrushing && !props?.disableDocBrushing) {
+            if (doc && !doc.layout_disableBrushing && !props?.disableBrushing) {
                 const selected = SelectionManager.Views().some(dv => dv.rootDoc === doc);
                 const highlightIndex = Doc.isBrushedHighlightedDegree(doc) || (selected ? Doc.DocBrushStatus.selfBrushed : 0);
                 const highlightColor = ['transparent', 'rgb(68, 118, 247)', selected ? 'black' : 'rgb(68, 118, 247)', 'orange', 'lightBlue'][highlightIndex];
@@ -166,7 +166,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
             if (!backColor) return undefined;
             return lightOrDark(backColor);
         case StyleProp.BorderRounding:
-            return StrCast(doc?.[fieldKey + 'borderRounding'], StrCast(doc?.borderRounding, doc?._type_collection === CollectionViewType.Pile ? '50%' : ''));
+            return StrCast(doc?.[fieldKey + 'borderRounding'], StrCast(doc?.layout_borderRounding, doc?._type_collection === CollectionViewType.Pile ? '50%' : ''));
         case StyleProp.BorderPath:
             const borderPath = Doc.IsComicStyle(doc) &&
                 props?.renderDepth &&
@@ -235,38 +235,37 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
         }
         case StyleProp.BoxShadow: {
             if (!doc || opacity() === 0 || doc.noShadow) return undefined; // if it's not visible, then no shadow)
-            if (doc.boxShadow === 'standard') return Shadows.STANDARD_SHADOW;
+            if (doc.layout_boxShadow === 'standard') return Shadows.STANDARD_SHADOW;
             if (IsFollowLinkScript(doc?.onClick) && LinkManager.Links(doc).length && ![DocumentType.LINK, DocumentType.INK].includes(doc.type as any)) return StrCast(doc?._linkButtonShadow, 'lightblue 0em 0em 1em');
             switch (doc?.type) {
                 case DocumentType.COL:
                     return StrCast(
-                        doc?.boxShadow,
+                        doc?.layout_borderRounding,
                         doc?._type_collection === CollectionViewType.Pile
                             ? '4px 4px 10px 2px'
                             : lockedPosition() || doc?._isGroup || docProps?.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)
-                            : `${darkScheme() ? Colors.DARK_GRAY : Colors.MEDIUM_GRAY} ${StrCast(doc.boxShadow, '0.2vw 0.2vw 0.8vw')}`
+                            : `${darkScheme() ? Colors.DARK_GRAY : Colors.MEDIUM_GRAY} ${StrCast(doc.layout_borderRounding, '0.2vw 0.2vw 0.8vw')}`
                     );
 
                 case DocumentType.LABEL:
                     if (doc?.annotationOn !== undefined) return 'black 2px 2px 1px';
                 default:
                     return doc.z
-                        ? `#9c9396  ${StrCast(doc?.boxShadow, '10px 10px 0.9vw')}` // if it's a floating doc, give it a big shadow
+                        ? `#9c9396  ${StrCast(doc?.layout_boxShadow, '10px 10px 0.9vw')}` // if it's a floating doc, give it a big shadow
                         : props?.docViewPath().lastElement()?.rootDoc._freeform_useClusters
-                        ? `${backgroundCol()} ${StrCast(doc.boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (docProps?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
+                        ? `${backgroundCol()} ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (docProps?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
                         : NumCast(doc.group, -1) !== -1
-                        ? `gray ${StrCast(doc.boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (docProps?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
+                        ? `gray ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (docProps?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
                         : lockedPosition()
                         ? undefined // if it's a background & has a cluster color, make the shadow spread really big
-                        : StrCast(doc.boxShadow, '');
+                        : StrCast(doc.layout_boxShadow, '');
             }
         }
         case StyleProp.PointerEvents:
             const isInk = doc && StrCast(Doc.Layout(doc).layout).includes(InkingStroke.name) && !props?.LayoutTemplateString;
             if (docProps?.DocumentView?.().ComponentView?.overridePointerEvents?.() !== undefined) return docProps?.DocumentView?.().ComponentView?.overridePointerEvents?.();
             if (MainView.Instance._exploreMode || doc?.layout_unrendered) return isInk ? 'visiblePainted' : 'all';
-            if (doc?.pointerEvents) return StrCast(doc.pointerEvents);
             if (props?.contentPointerEvents) return StrCast(props.contentPointerEvents);
             if (props?.pointerEvents?.() === 'none') return 'none';
             if (opacity() === 0) return 'none';
@@ -284,9 +283,9 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
             };
             const filter = () => {
                 const showFilterIcon =
-                    StrListCast(doc?._docFilters).length || StrListCast(doc?._docRangeFilters).length
+                    StrListCast(doc?._childFilters).length || StrListCast(doc?._childFiltersByRanges).length
                         ? '#18c718bd' //'hasFilter'
-                        : docProps?.docFilters?.().filter(f => Utils.IsRecursiveFilter(f) && f !== Utils.noDragsDocFilter).length || docProps?.docRangeFilters().length
+                        : docProps?.childFilters?.().filter(f => Utils.IsRecursiveFilter(f) && f !== Utils.noDragsDocFilter).length || docProps?.childFiltersByRanges().length
                         ? 'orange' //'inheritsFilter'
                         : undefined;
                 return !showFilterIcon ? null : (
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 814a95ab8..62f47ba71 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -116,8 +116,8 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
                     styleProvider={DefaultStyleProvider}
                     setHeight={returnFalse}
                     docViewPath={returnEmptyDoclist}
-                    docFilters={returnEmptyFilter}
-                    docRangeFilters={returnEmptyFilter}
+                    childFilters={returnEmptyFilter}
+                    childFiltersByRanges={returnEmptyFilter}
                     searchFilterDocs={returnEmptyDoclist}
                     rootSelected={returnFalse}
                     onCheckedClick={this.scriptField}
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index dc430667b..fc3f713ce 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -130,8 +130,8 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
                     renderDepth={0}
                     focus={emptyFunction}
                     whenChildContentsActiveChanged={emptyFunction}
-                    docFilters={returnEmptyFilter}
-                    docRangeFilters={returnEmptyFilter}
+                    childFilters={returnEmptyFilter}
+                    childFiltersByRanges={returnEmptyFilter}
                     searchFilterDocs={returnEmptyDoclist}
                 />
             </div>
@@ -277,16 +277,16 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
     _saveFilterCommand = {
         params: ['target'],
         title: 'save filter',
-        script: `self.target._docFilters = compareLists(self['target-docFilters'],self.target._docFilters) ? undefined : copyField(self['target-docFilters']); 
+        script: `self.target._childFilters = compareLists(self['target-childFilters'],self.target._childFilters) ? undefined : copyField(self['target-childFilters']); 
                  self.target._searchFilterDocs = compareLists(self['target-searchFilterDocs'],self.target._searchFilterDocs) ? undefined: copyField(self['target-searchFilterDocs']);`,
         immediate: undoBatch((source: Doc[]) => {
-            this.target._docFilters = undefined;
+            this.target._childFilters = undefined;
             this.target._searchFilterDocs = undefined;
         }),
         initialize: (button: Doc) => {
             const activeDash = Doc.ActiveDashboard;
             if (activeDash) {
-                button['target-docFilters'] = (Doc.MySearcher._docFilters || activeDash._docFilters) instanceof ObjectField ? ObjectField.MakeCopy((Doc.MySearcher._docFilters || activeDash._docFilters) as any as ObjectField) : undefined;
+                button['target-childFilters'] = (Doc.MySearcher._childFilters || activeDash._childFilters) instanceof ObjectField ? ObjectField.MakeCopy((Doc.MySearcher._childFilters || activeDash._childFilters) as any as ObjectField) : undefined;
                 button['target-searchFilterDocs'] = activeDash._searchFilterDocs instanceof ObjectField ? ObjectField.MakeCopy(activeDash._searchFilterDocs as any as ObjectField) : undefined;
             }
         },
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index fc265e2b9..675f23970 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -201,13 +201,17 @@ export class CollectionNoteTakingView extends CollectionSubView() {
     };
 
     styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => {
-        if (property === StyleProp.BoxShadow && doc && DragManager.docsBeingDragged.includes(doc)) {
-            return `#9c9396  ${StrCast(doc?.boxShadow, '10px 10px 0.9vw')}`;
-        }
-        if (property === StyleProp.Opacity && doc) {
-            if (this.props.childOpacity) {
-                return this.props.childOpacity();
-            }
+        switch (property) {
+            case StyleProp.BoxShadow:
+                if (doc && DragManager.docsBeingDragged.includes(doc)) {
+                    return `#9c9396 ${StrCast(doc?.layout_boxShadow, '10px 10px 0.9vw')}`;
+                }
+                break;
+            case StyleProp.Opacity:
+                if (doc && this.props.childOpacity) {
+                    return this.props.childOpacity();
+                }
+                break;
         }
         return this.props.styleProvider?.(doc, props, property);
     };
@@ -251,11 +255,11 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                 onDoubleClick={this.onChildDoubleClickHandler}
                 ScreenToLocalTransform={noteTakingDocTransform}
                 focus={this.focusDocument}
-                docFilters={this.childDocFilters}
+                childFilters={this.childDocFilters}
                 hideDecorationTitle={this.props.childHideDecorationTitle?.()}
                 hideResizeHandles={this.props.childHideResizeHandles?.()}
                 hideTitle={this.props.childHideTitle?.()}
-                docRangeFilters={this.childDocRangeFilters}
+                childFiltersByRanges={this.childDocRangeFilters}
                 searchFilterDocs={this.searchFilterDocs}
                 addDocument={this.props.addDocument}
                 moveDocument={this.props.moveDocument}
@@ -603,42 +607,6 @@ export class CollectionNoteTakingView extends CollectionSubView() {
         return eles;
     }
 
-    @computed get buttonMenu() {
-        const menuDoc: Doc = Cast(this.rootDoc.buttonMenuDoc, Doc, null);
-        if (menuDoc) {
-            const width = NumCast(menuDoc._width, 30);
-            const height = NumCast(menuDoc._height, 30);
-            return (
-                <div className="buttonMenu-docBtn" style={{ width, height }}>
-                    <DocumentView
-                        Document={menuDoc}
-                        DataDoc={menuDoc}
-                        isContentActive={this.props.isContentActive}
-                        isDocumentActive={returnTrue}
-                        addDocument={this.props.addDocument}
-                        moveDocument={this.props.moveDocument}
-                        addDocTab={this.props.addDocTab}
-                        pinToPres={emptyFunction}
-                        rootSelected={this.props.isSelected}
-                        removeDocument={this.props.removeDocument}
-                        ScreenToLocalTransform={Transform.Identity}
-                        PanelWidth={() => 35}
-                        PanelHeight={() => 35}
-                        renderDepth={this.props.renderDepth}
-                        focus={emptyFunction}
-                        styleProvider={this.props.styleProvider}
-                        docViewPath={returnEmptyDoclist}
-                        whenChildContentsActiveChanged={emptyFunction}
-                        bringToFront={emptyFunction}
-                        docFilters={this.props.docFilters}
-                        docRangeFilters={this.props.docRangeFilters}
-                        searchFilterDocs={this.props.searchFilterDocs}
-                    />
-                </div>
-            );
-        }
-    }
-
     @computed get nativeWidth() {
         return Doc.NativeWidth(this.layoutDoc);
     }
@@ -658,35 +626,25 @@ export class CollectionNoteTakingView extends CollectionSubView() {
 
     render() {
         TraceMobx();
-        const buttonMenu = this.rootDoc.buttonMenu;
-        const noviceExplainer = StrCast(this.rootDoc.explainer);
         return (
-            <>
-                {buttonMenu || noviceExplainer ? (
-                    <div className="documentButtonMenu" key="buttons">
-                        {buttonMenu ? this.buttonMenu : null}
-                        {Doc.UserDoc().noviceMode && noviceExplainer ? <div className="documentExplanation">{noviceExplainer}</div> : null}
-                    </div>
-                ) : null}
-                <div
-                    className="collectionNoteTakingView"
-                    ref={this.createRef}
-                    key="notes"
-                    style={{
-                        overflowY: this.props.isContentActive() ? 'auto' : 'hidden',
-                        background: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor),
-                        pointerEvents: this.backgroundEvents ? 'all' : undefined,
-                    }}
-                    onScroll={action(e => (this._scroll = e.currentTarget.scrollTop))}
-                    onPointerLeave={action(e => (this.docsDraggedRowCol.length = 0))}
-                    onPointerMove={e => e.buttons && this.onPointerMove(false, e.clientX, e.clientY)}
-                    onDragOver={e => this.onPointerMove(true, e.clientX, e.clientY)}
-                    onDrop={this.onExternalDrop.bind(this)}
-                    onContextMenu={this.onContextMenu}
-                    onWheel={e => this.props.isContentActive(true) && e.stopPropagation()}>
-                    {this.renderedSections}
-                </div>
-            </>
+            <div
+                className="collectionNoteTakingView"
+                ref={this.createRef}
+                key="notes"
+                style={{
+                    overflowY: this.props.isContentActive() ? 'auto' : 'hidden',
+                    background: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor),
+                    pointerEvents: this.backgroundEvents ? 'all' : undefined,
+                }}
+                onScroll={action(e => (this._scroll = e.currentTarget.scrollTop))}
+                onPointerLeave={action(e => (this.docsDraggedRowCol.length = 0))}
+                onPointerMove={e => e.buttons && this.onPointerMove(false, e.clientX, e.clientY)}
+                onDragOver={e => this.onPointerMove(true, e.clientX, e.clientY)}
+                onDrop={this.onExternalDrop.bind(this)}
+                onContextMenu={this.onContextMenu}
+                onWheel={e => this.props.isContentActive(true) && e.stopPropagation()}>
+                {this.renderedSections}
+            </div>
         );
     }
 }
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index bbc91aa6e..57d9bbb49 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -23,7 +23,7 @@ export class CollectionPileView extends CollectionSubView() {
 
     componentDidMount() {
         if (this.layoutEngine() !== computePassLayout.name && this.layoutEngine() !== computeStarburstLayout.name) {
-            this.Document._pileLayoutEngine = computePassLayout.name;
+            this.Document._freeform_pileEngine = computePassLayout.name;
         }
         this._originalChrome = this.layoutDoc._chromeHidden;
         this.layoutDoc._chromeHidden = true;
@@ -33,7 +33,7 @@ export class CollectionPileView extends CollectionSubView() {
         Object.values(this._disposers).forEach(disposer => disposer?.());
     }
 
-    layoutEngine = () => StrCast(this.Document._pileLayoutEngine);
+    layoutEngine = () => StrCast(this.Document._freeform_pileEngine);
 
     @undoBatch
     addPileDoc = (doc: Doc | Doc[]) => {
@@ -73,28 +73,29 @@ export class CollectionPileView extends CollectionSubView() {
 
     // toggles the pileup between starburst to compact
     toggleStarburst = action(() => {
+        this.layoutDoc._freeform_scale = undefined;
         if (this.layoutEngine() === computeStarburstLayout.name) {
             if (this.rootDoc[Width]() !== NumCast(this.rootDoc._starburstDiameter, 500)) {
                 this.rootDoc._starburstDiameter = this.rootDoc[Width]();
             }
             const defaultSize = 110;
-            this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[Width]() / 2 - NumCast(this.layoutDoc._starburstPileWidth, defaultSize) / 2;
-            this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[Height]() / 2 - NumCast(this.layoutDoc._starburstPileHeight, defaultSize) / 2;
-            this.layoutDoc._width = NumCast(this.layoutDoc._starburstPileWidth, defaultSize);
-            this.layoutDoc._height = NumCast(this.layoutDoc._starburstPileHeight, defaultSize);
+            this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[Width]() / 2 - NumCast(this.layoutDoc._freeform_pileWidth, defaultSize) / 2;
+            this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[Height]() / 2 - NumCast(this.layoutDoc._freeform_pileHeight, defaultSize) / 2;
+            this.layoutDoc._width = NumCast(this.layoutDoc._freeform_pileWidth, defaultSize);
+            this.layoutDoc._height = NumCast(this.layoutDoc._freeform_pileHeight, defaultSize);
             DocUtils.pileup(this.childDocs, undefined, undefined, NumCast(this.layoutDoc._width) / 2, false);
             this.layoutDoc._freeform_panX = 0;
             this.layoutDoc._freeform_panY = -10;
-            this.props.Document._pileLayoutEngine = computePassLayout.name;
+            this.props.Document._freeform_pileEngine = computePassLayout.name;
         } else {
             const defaultSize = NumCast(this.rootDoc._starburstDiameter, 500);
             this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[Width]() / 2 - defaultSize / 2;
             this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[Height]() / 2 - defaultSize / 2;
-            this.layoutDoc._starburstPileWidth = this.layoutDoc[Width]();
-            this.layoutDoc._starburstPileHeight = this.layoutDoc[Height]();
+            this.layoutDoc._freeform_pileWidth = this.layoutDoc[Width]();
+            this.layoutDoc._freeform_pileHeight = this.layoutDoc[Height]();
             this.layoutDoc._freeform_panX = this.layoutDoc._freeform_panY = 0;
             this.layoutDoc._width = this.layoutDoc._height = defaultSize;
-            this.props.Document._pileLayoutEngine = computeStarburstLayout.name;
+            this.props.Document._freeform_pileEngine = computeStarburstLayout.name;
         }
     });
 
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index b131d38d8..9d5cb257a 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -404,15 +404,15 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
             docAnchor ??
             Docs.Create.LabelDocument({
                 title: ComputedField.MakeFunction(`self["${endTag}"] ? "#" + formatToTime(self["${startTag}"]) + "-" + formatToTime(self["${endTag}"]) : "#" + formatToTime(self["${startTag}"])`) as any,
-                _minFontSize: 12,
-                _maxFontSize: 24,
+                _label_minFontSize: 12,
+                _label_maxFontSize: 24,
                 _stayInCollection: true,
                 backgroundColor: 'rgba(128, 128, 128, 0.5)',
                 layout_hideLinkButton: true,
                 onClick: FollowLinkScript(),
                 annotationOn: rootDoc,
-                _timelineLabel: true,
-                borderRounding: anchorEndTime === undefined ? '100%' : undefined,
+                _isTimelineLabel: true,
+                layout_borderRounding: anchorEndTime === undefined ? '100%' : undefined,
             });
         Doc.GetProto(anchor)[startTag] = anchorStartTime;
         Doc.GetProto(anchor)[endTag] = anchorEndTime;
@@ -752,7 +752,7 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
             if (timelineOnly) {
                 if (!left && time !== undefined && time <= NumCast(anchor[this.props.startTag])) time = undefined;
                 Doc.SetInPlace(anchor, left ? this.props.startTag : this.props.endTag, time, true);
-                if (!left) Doc.SetInPlace(anchor, 'borderRounding', time !== undefined ? undefined : '100%', true);
+                if (!left) Doc.SetInPlace(anchor, 'layout_borderRounding', time !== undefined ? undefined : '100%', true);
             } else {
                 anchor[left ? '_timecodeToShow' : '_timecodeToHide'] = time;
             }
@@ -820,8 +820,8 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
                     focus={focusFunc}
                     isContentActive={returnFalse}
                     searchFilterDocs={returnEmptyDoclist}
-                    docFilters={returnEmptyFilter}
-                    docRangeFilters={returnEmptyFilter}
+                    childFilters={returnEmptyFilter}
+                    childFiltersByRanges={returnEmptyFilter}
                     rootSelected={returnFalse}
                     onClick={script}
                     onDoubleClick={this.props.layoutDoc.autoPlayAnchors ? undefined : doublescript}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 56f53afe6..3c0d8cbc3 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -346,11 +346,11 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 onDoubleClick={this.onChildDoubleClickHandler}
                 ScreenToLocalTransform={stackedDocTransform}
                 focus={this.focusDocument}
-                docFilters={this.childDocFilters}
+                childFilters={this.childDocFilters}
                 hideDecorationTitle={this.props.childHideDecorationTitle?.()}
                 hideResizeHandles={this.props.childHideResizeHandles?.()}
                 hideTitle={this.props.childHideTitle?.()}
-                docRangeFilters={this.childDocRangeFilters}
+                childFiltersByRanges={this.childDocRangeFilters}
                 searchFilterDocs={this.searchFilterDocs}
                 xPadding={NumCast(this.layoutDoc._childXPadding, this.props.childXPadding)}
                 yPadding={NumCast(this.layoutDoc._childYPadding, this.props.childYPadding)}
@@ -650,7 +650,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
 
     return35 = () => 35;
     @computed get buttonMenu() {
-        const menuDoc: Doc = Cast(this.rootDoc.buttonMenuDoc, Doc, null);
+        const menuDoc: Doc = Cast(this.rootDoc.layout_headerButton, Doc, null);
         // TODO:glr Allow support for multiple buttons
         if (menuDoc) {
             const width: number = NumCast(menuDoc._width, 30);
@@ -679,8 +679,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                         docViewPath={returnEmptyDoclist}
                         whenChildContentsActiveChanged={emptyFunction}
                         bringToFront={emptyFunction}
-                        docFilters={this.props.docFilters}
-                        docRangeFilters={this.props.docRangeFilters}
+                        childFilters={this.props.childFilters}
+                        childFiltersByRanges={this.props.childFiltersByRanges}
                         searchFilterDocs={this.props.searchFilterDocs}
                     />
                 </div>
@@ -711,7 +711,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
             contents: '+ ADD A GROUP',
         };
         const buttonMenu = this.rootDoc.buttonMenu;
-        const noviceExplainer = this.rootDoc.explainer;
+        const noviceExplainer = this.rootDoc.layout_explainer;
         return (
             <>
                 {buttonMenu || noviceExplainer ? (
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 967d1d40e..d1b7f6ff6 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -28,8 +28,8 @@ export function CollectionSubView<X>(moreProps?: X) {
         private gestureDisposer?: GestureUtils.GestureEventDisposer;
         protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
         protected _mainCont?: HTMLDivElement;
-        @observable _focusFilters: Opt<string[]>; // docFilters that are overridden when previewing a link to an anchor which has docFilters set on it
-        @observable _focusRangeFilters: Opt<string[]>; // docRangeFilters that are overridden when previewing a link to an anchor which has docRangeFilters set on it
+        @observable _focusFilters: Opt<string[]>; // childFilters that are overridden when previewing a link to an anchor which has childFilters set on it
+        @observable _focusRangeFilters: Opt<string[]>; // childFiltersByRanges that are overridden when previewing a link to an anchor which has childFiltersByRanges set on it
         protected createDashEventsTarget = (ele: HTMLDivElement | null) => {
             //used for stacking and masonry view
             this.dropDisposer?.();
@@ -81,13 +81,13 @@ export function CollectionSubView<X>(moreProps?: X) {
         get childDocList() {
             return Cast(this.dataField, listSpec(Doc));
         }
-        collectionFilters = () => this._focusFilters ?? StrListCast(this.props.Document._docFilters);
-        collectionRangeDocFilters = () => this._focusRangeFilters ?? Cast(this.props.Document._docRangeFilters, listSpec('string'), []);
+        collectionFilters = () => this._focusFilters ?? StrListCast(this.props.Document._childFilters);
+        collectionRangeDocFilters = () => this._focusRangeFilters ?? Cast(this.props.Document._childFiltersByRanges, listSpec('string'), []);
         // child filters apply to the descendants of the documents in this collection
-        childDocFilters = () => [...(this.props.docFilters?.().filter(f => Utils.IsRecursiveFilter(f)) || []), ...this.collectionFilters()];
+        childDocFilters = () => [...(this.props.childFilters?.().filter(f => Utils.IsRecursiveFilter(f)) || []), ...this.collectionFilters()];
         // unrecursive filters apply to the documents in the collection, but no their children.  See Utils.noRecursionHack
-        unrecursiveDocFilters = () => [...(this.props.docFilters?.().filter(f => !Utils.IsRecursiveFilter(f)) || [])];
-        childDocRangeFilters = () => [...(this.props.docRangeFilters?.() || []), ...this.collectionRangeDocFilters()];
+        unrecursiveDocFilters = () => [...(this.props.childFilters?.().filter(f => !Utils.IsRecursiveFilter(f)) || [])];
+        childDocRangeFilters = () => [...(this.props.childFiltersByRanges?.() || []), ...this.collectionRangeDocFilters()];
         searchFilterDocs = () => this.props.searchFilterDocs?.() ?? DocListCast(this.props.Document._searchFilterDocs);
         @computed.struct get childDocs() {
             TraceMobx();
@@ -109,20 +109,20 @@ export function CollectionSubView<X>(moreProps?: X) {
             const childDocs = rawdocs.filter(d => !(d instanceof Promise) && GetEffectiveAcl(Doc.GetProto(d)) !== AclPrivate && (this.props.ignoreUnrendered || !d.layout_unrendered)).map(d => d as Doc);
 
             const childDocFilters = this.childDocFilters();
-            const docRangeFilters = this.childDocRangeFilters();
+            const childFiltersByRanges = this.childDocRangeFilters();
             const searchDocs = this.searchFilterDocs();
-            if (this.props.Document.dontRegisterView || (!childDocFilters.length && !this.unrecursiveDocFilters().length && !docRangeFilters.length && !searchDocs.length)) {
+            if (this.props.Document.dontRegisterView || (!childDocFilters.length && !this.unrecursiveDocFilters().length && !childFiltersByRanges.length && !searchDocs.length)) {
                 return childDocs.filter(cd => !cd.cookies); // remove any documents that require a cookie if there are no filters to provide one
             }
 
             const docsforFilter: Doc[] = [];
             childDocs.forEach(d => {
                 // dragging facets
-                const dragged = this.props.docFilters?.().some(f => f.includes(Utils.noDragsDocFilter));
+                const dragged = this.props.childFilters?.().some(f => f.includes(Utils.noDragsDocFilter));
                 if (dragged && DragManager.docsBeingDragged.includes(d)) return false;
-                let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), docRangeFilters, this.props.Document).length > 0;
+                let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), childFiltersByRanges, this.props.Document).length > 0;
                 if (notFiltered) {
-                    notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, docRangeFilters, this.props.Document).length > 0;
+                    notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, childFiltersByRanges, this.props.Document).length > 0;
                     const fieldKey = Doc.LayoutFieldKey(d);
                     const annos = !Field.toString(Doc.LayoutField(d) as Field).includes(CollectionView.name);
                     const data = d[annos ? fieldKey + '_annotations' : fieldKey];
@@ -131,13 +131,14 @@ export function CollectionSubView<X>(moreProps?: X) {
                         let subDocs = [...DocListCast(data), ...DocListCast(side)];
                         if (subDocs.length > 0) {
                             let newarray: Doc[] = [];
-                            notFiltered = notFiltered || (!searchDocs.length && DocUtils.FilterDocs(subDocs, childDocFilters, docRangeFilters, d).length);
+                            notFiltered = notFiltered || (!searchDocs.length && DocUtils.FilterDocs(subDocs, childDocFilters, childFiltersByRanges, d).length);
                             while (subDocs.length > 0 && !notFiltered) {
                                 newarray = [];
                                 subDocs.forEach(t => {
                                     const fieldKey = Doc.LayoutFieldKey(t);
                                     const annos = !Field.toString(Doc.LayoutField(t) as Field).includes(CollectionView.name);
-                                    notFiltered = notFiltered || ((!searchDocs.length || searchDocs.includes(t)) && ((!childDocFilters.length && !docRangeFilters.length) || DocUtils.FilterDocs([t], childDocFilters, docRangeFilters, d).length));
+                                    notFiltered =
+                                        notFiltered || ((!searchDocs.length || searchDocs.includes(t)) && ((!childDocFilters.length && !childFiltersByRanges.length) || DocUtils.FilterDocs([t], childDocFilters, childFiltersByRanges, d).length));
                                     DocListCast(t[annos ? fieldKey + '_annotations' : fieldKey]).forEach(newdoc => newarray.push(newdoc));
                                     annos && DocListCast(t[fieldKey + '_sidebar']).forEach(newdoc => newarray.push(newdoc));
                                 });
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 49a90d828..60e6815e5 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -125,8 +125,8 @@ export class CollectionTimeView extends CollectionSubView() {
 
     goTo = (prevFilterIndex: number) => {
         this.layoutDoc._pivotField = this.layoutDoc['_prevPivotFields' + prevFilterIndex];
-        this.layoutDoc._docFilters = ObjectField.MakeCopy(this.layoutDoc['_prevDocFilter' + prevFilterIndex] as ObjectField);
-        this.layoutDoc._docRangeFilters = ObjectField.MakeCopy(this.layoutDoc['_prevDocRangeFilters' + prevFilterIndex] as ObjectField);
+        this.layoutDoc._childFilters = ObjectField.MakeCopy(this.layoutDoc['_prevDocFilter' + prevFilterIndex] as ObjectField);
+        this.layoutDoc._childFiltersByRanges = ObjectField.MakeCopy(this.layoutDoc['_prevDocRangeFilters' + prevFilterIndex] as ObjectField);
         this.layoutDoc._prevFilterIndex = prevFilterIndex;
     };
 
@@ -136,7 +136,7 @@ export class CollectionTimeView extends CollectionSubView() {
         if (prevFilterIndex > 0) {
             this.goTo(prevFilterIndex - 1);
         } else {
-            this.layoutDoc._docFilters = new List([]);
+            this.layoutDoc._childFilters = new List([]);
         }
     };
 
@@ -145,7 +145,7 @@ export class CollectionTimeView extends CollectionSubView() {
             <div className="collectionTimeView-innards" key="timeline" style={{ pointerEvents: this.props.isContentActive() ? undefined : 'none' }} onClick={this.contentsDown}>
                 <CollectionFreeFormView
                     {...this.props}
-                    engineProps={{ pivotField: this.pivotField, docFilters: this.childDocFilters, docRangeFilters: this.childDocRangeFilters }}
+                    engineProps={{ pivotField: this.pivotField, childFilters: this.childDocFilters, childFiltersByRanges: this.childDocRangeFilters }}
                     fitContentsToBox={returnTrue}
                     childClickScript={this._childClickedScript}
                     viewDefDivClick={this.layoutEngine() === computeTimelineLayout.name ? undefined : this._viewDefDivClick}
@@ -276,12 +276,12 @@ export class CollectionTimeView extends CollectionSubView() {
 ScriptingGlobals.add(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBounds) {
     const pivotField = StrCast(pivotDoc._pivotField, 'author');
     let prevFilterIndex = NumCast(pivotDoc._prevFilterIndex);
-    const originalFilter = StrListCast(ObjectField.MakeCopy(pivotDoc._docFilters as ObjectField));
-    pivotDoc['_prevDocFilter' + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docFilters as ObjectField);
-    pivotDoc['_prevDocRangeFilters' + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docRangeFilters as ObjectField);
+    const originalFilter = StrListCast(ObjectField.MakeCopy(pivotDoc._childFilters as ObjectField));
+    pivotDoc['_prevDocFilter' + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._childFilters as ObjectField);
+    pivotDoc['_prevDocRangeFilters' + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._childFiltersByRanges as ObjectField);
     pivotDoc['_prevPivotFields' + prevFilterIndex] = pivotField;
     pivotDoc._prevFilterIndex = ++prevFilterIndex;
-    pivotDoc._docFilters = new List();
+    pivotDoc._childFilters = new List();
     setTimeout(
         action(() => {
             const filterVals = bounds.payload as string[];
@@ -292,7 +292,7 @@ ScriptingGlobals.add(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBou
                     pivotDoc._pivotField = filterVals[0];
                 }
             }
-            const newFilters = StrListCast(pivotDoc._docFilters);
+            const newFilters = StrListCast(pivotDoc._childFilters);
             if (newFilters.length && originalFilter.length && newFilters.lastElement() === originalFilter.lastElement()) {
                 pivotDoc._prevFilterIndex = --prevFilterIndex;
                 pivotDoc['_prevDocFilter' + prevFilterIndex] = undefined;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 969658238..ed1e0c067 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -240,8 +240,8 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
                 PanelHeight={this.documentTitleHeight}
                 NativeDimScaling={returnOne}
                 onKey={this.onKey}
-                docFilters={returnEmptyFilter}
-                docRangeFilters={returnEmptyFilter}
+                childFilters={returnEmptyFilter}
+                childFiltersByRanges={returnEmptyFilter}
                 searchFilterDocs={returnEmptyDoclist}
                 addDocument={returnFalse}
                 moveDocument={returnFalse}
@@ -314,12 +314,12 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     }
 
     @computed get noviceExplainer() {
-        return !Doc.noviceMode || !this.rootDoc.explainer ? null : <div className="documentExplanation"> {StrCast(this.rootDoc.explainer)} </div>;
+        return !Doc.noviceMode || !this.rootDoc.layout_explainer ? null : <div className="documentExplanation"> {StrCast(this.rootDoc.layout_explainer)} </div>;
     }
 
     return35 = () => 35;
     @computed get buttonMenu() {
-        const menuDoc = Cast(this.rootDoc.buttonMenuDoc, Doc, null);
+        const menuDoc = Cast(this.rootDoc.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) }}>
@@ -343,8 +343,8 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
                     docViewPath={returnEmptyDoclist}
                     whenChildContentsActiveChanged={emptyFunction}
                     bringToFront={emptyFunction}
-                    docFilters={this.props.docFilters}
-                    docRangeFilters={this.props.docRangeFilters}
+                    childFilters={this.props.childFilters}
+                    childFiltersByRanges={this.props.childFiltersByRanges}
                     searchFilterDocs={this.props.searchFilterDocs}
                 />
             </div>
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 710e33b1c..75e4e8abf 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -432,8 +432,8 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                     PanelWidth={this.PanelWidth}
                     PanelHeight={this.PanelHeight}
                     styleProvider={DefaultStyleProvider}
-                    docFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDoclist}
-                    docRangeFilters={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDoclist}
+                    childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDoclist}
+                    childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDoclist}
                     searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist}
                     addDocument={undefined}
                     removeDocument={this.remDocTab}
@@ -602,8 +602,8 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
                     styleProvider={TabMinimapView.miniStyleProvider}
                     addDocTab={this.props.addDocTab}
                     pinToPres={TabDocView.PinDoc}
-                    docFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDoclist}
-                    docRangeFilters={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDoclist}
+                    childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDoclist}
+                    childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDoclist}
                     searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist}
                     fitContentsToBox={returnTrue}
                 />
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index ca9471f6f..9039875c1 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -119,7 +119,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         return 'TreeView(' + this.props.document.title + ')';
     } // this makes mobx trace() statements more descriptive
     get defaultExpandedView() {
-        return this.doc.type_collection === CollectionViewType.Docking
+        return this.doc._type_collection === CollectionViewType.Docking
             ? this.fieldKey
             : this.props.treeView.dashboardMode
             ? this.fieldKey
@@ -388,7 +388,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         };
         const addDoc = inside ? localAdd : parentAddDoc;
         const move = (!dropAction || dropAction === 'proto' || dropAction === 'move' || dropAction === 'same') && moveDocument;
-        const canAdd = (!this.props.treeView.outlineMode && !StrCast((inside ? this.props.document : this.props.treeViewParent)?.freezeChildren).includes('add')) || forceAdd;
+        const canAdd = (!this.props.treeView.outlineMode && !StrCast((inside ? this.props.document : this.props.treeViewParent)?.treeViewFreezeChildren).includes('add')) || forceAdd;
         if (canAdd) {
             this.props.parentTreeView instanceof TreeView && (this.props.parentTreeView.dropping = true);
             const res = UndoManager.RunInTempBatch(() => droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (dropAction === 'proto' ? addDoc(d) : false) : addDoc(d)) || added, false));
@@ -714,7 +714,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         const data = () => (this.childDocs || this.props.treeView.dashboardMode ? this.fieldKey : '');
         const embeddings = () => (this.props.treeView.dashboardMode ? '' : 'embeddings');
         const fields = () => (Doc.noviceMode ? '' : 'fields');
-        const layout = Doc.noviceMode || this.doc.type_collection === CollectionViewType.Docking ? [] : ['layout'];
+        const layout = Doc.noviceMode || this.doc._type_collection === CollectionViewType.Docking ? [] : ['layout'];
         return [data(), ...layout, ...(this.props.treeView.fileSysMode ? [embeddings(), links(), annos()] : []), fields()].filter(m => m);
     }
     @action
@@ -732,7 +732,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         return this.props.treeViewHideHeaderFields() || this.doc.treeViewHideHeaderFields ? null : (
             <>
                 {customHeaderButtons} {/* e.g.,. hide button is set by dashboardStyleProvider */}
-                {this.doc.hideContextMenu ? null : (
+                {this.doc._layout_hideContextMenu ? null : (
                     <FontAwesomeIcon
                         title="context menu"
                         key="bars"
@@ -772,7 +772,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                 ? []
                 : this.props.treeView.fileSysMode && this.doc === Doc.GetProto(this.doc)
                 ? [openEmbedding, makeFolder]
-                : this.doc.type_collection === CollectionViewType.Docking
+                : this.doc._type_collection === CollectionViewType.Docking
                 ? []
                 : [deleteItem, openEmbedding, focusDoc]),
         ];
@@ -930,13 +930,13 @@ export class TreeView extends React.Component<TreeViewProps> {
                 focus={this.refocus}
                 whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
                 bringToFront={emptyFunction}
-                disableDocBrushing={this.props.treeView.props.disableDocBrushing}
+                disableBrushing={this.props.treeView.props.disableBrushing}
                 hideLinkButton={BoolCast(this.props.treeView.props.Document.childHideLinkButton)}
                 dontRegisterView={BoolCast(this.props.treeView.props.Document.childDontRegisterViews, this.props.dontRegisterView)}
                 xPadding={NumCast(this.props.treeView.props.Document.childXPadding, this.props.treeView.props.childXPadding)}
                 yPadding={NumCast(this.props.treeView.props.Document.childYPadding, this.props.treeView.props.childYPadding)}
-                docFilters={returnEmptyFilter}
-                docRangeFilters={returnEmptyFilter}
+                childFilters={returnEmptyFilter}
+                childFiltersByRanges={returnEmptyFilter}
                 searchFilterDocs={returnEmptyDoclist}
             />
         );
@@ -1010,8 +1010,8 @@ export class TreeView extends React.Component<TreeViewProps> {
                     treeViewDoc={this.props.treeView?.props.Document}
                     rootSelected={returnTrue}
                     docViewPath={this.props.treeView.props.docViewPath}
-                    docFilters={returnEmptyFilter}
-                    docRangeFilters={returnEmptyFilter}
+                    childFilters={returnEmptyFilter}
+                    childFiltersByRanges={returnEmptyFilter}
                     searchFilterDocs={returnEmptyDoclist}
                     addDocument={this.props.addDocument}
                     moveDocument={this.move}
@@ -1021,7 +1021,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                     yPadding={NumCast(this.props.treeView.props.Document.childYPadding, this.props.treeView.props.childYPadding)}
                     addDocTab={this.props.addDocTab}
                     pinToPres={this.props.treeView.props.pinToPres}
-                    disableDocBrushing={this.props.treeView.props.disableDocBrushing}
+                    disableBrushing={this.props.treeView.props.disableBrushing}
                     bringToFront={returnFalse}
                     scriptContext={this}
                 />
@@ -1211,7 +1211,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                         onCheckedClick={onCheckedClick}
                         onChildClick={onChildClick}
                         renderDepth={renderDepth}
-                        removeDoc={StrCast(treeViewParent.freezeChildren).includes('remove') ? undefined : remove}
+                        removeDoc={StrCast(treeViewParent.treeViewFreezeChildren).includes('remove') ? undefined : remove}
                         addDocument={addDocument}
                         styleProvider={styleProvider}
                         panelWidth={rowWidth}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 6202dcacc..f1d98d22a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -59,7 +59,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
             0
         ); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render()
         setTimeout(
-            action(() => (!LinkDocs.length || !linkDoc.layout_linkDisplay) && (this._opacity = 0.05)),
+            action(() => (!LinkDocs.length || !linkDoc.link_displayLine) && (this._opacity = 0.05)),
             750
         ); // this will unhighlight the link line.
         const a = A.ContentDiv.getBoundingClientRect();
@@ -77,7 +77,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
         const targetBhyperlink = Array.from(window.document.getElementsByClassName((linkDoc.link_anchor_2 as Doc)[Id])).lastElement();
         if ((!targetAhyperlink && !a.width) || (!targetBhyperlink && !b.width)) return;
         if (!targetAhyperlink) {
-            if (linkDoc.layout_autoMoveAnchors) {
+            if (linkDoc.link_autoMoveAnchors) {
                 linkDoc.link_anchor_1_x = ((apt.point.x - aleft) / awidth) * 100;
                 linkDoc.link_anchor_1_y = ((apt.point.y - atop) / aheight) * 100;
             }
@@ -92,7 +92,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
             else linkDoc.opacity = 1;
         }
         if (!targetBhyperlink) {
-            if (linkDoc.layout_autoMoveAnchors) {
+            if (linkDoc.link_autoMoveAnchors) {
                 linkDoc.link_anchor_2_x = ((bpt.point.x - bleft) / bwidth) * 100;
                 linkDoc.link_anchor_2_y = ((bpt.point.y - btop) / bheight) * 100;
             }
@@ -247,7 +247,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
         const { a, b, pt1norm, pt2norm, aActive, bActive, textX, textY, pt1, pt2 } = this.renderData;
         const linkRelationship = Field.toString(link?.link_relationship as any as Field); //get string representing relationship
         const linkRelationshipList = Doc.UserDoc().link_relationshipList as List<string>;
-        const linkColorList = Doc.UserDoc().linkColorList as List<string>;
+        const linkColorList = Doc.UserDoc().link_ColorList as List<string>;
         const linkRelationshipSizes = Doc.UserDoc().link_relationshipSizes as List<number>;
         const currRelationshipIndex = linkRelationshipList.indexOf(linkRelationship);
         const linkDescription = Field.toString(link.link_description as any as Field);
@@ -262,11 +262,11 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
         //thickness varies linearly from 3px to 12px for increasing link count
         const strokeWidth = linkSize === -1 ? '3px' : Math.floor(2 + 10 * (linkSize / Math.max(...linkRelationshipSizes))) + 'px';
 
-        if (link.layout_linkDisplayArrow === undefined) {
-            link.layout_linkDisplayArrow = false;
+        if (link.link_displayArrow === undefined) {
+            link.link_displayArrow = false;
         }
 
-        return link.opacity === 0 || !a.width || !b.width || (!link.layout_linkDisplay && !aActive && !bActive) ? null : (
+        return link.opacity === 0 || !a.width || !b.width || (!link.link_displayLine && !aActive && !bActive) ? null : (
             <>
                 <defs>
                     <marker id={`${link[Id] + 'arrowhead'}`} markerWidth="4" markerHeight="3" refX="0" refY="1.5" orient="auto">
@@ -295,7 +295,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
                     style={{ pointerEvents: 'visibleStroke', opacity: this._opacity, stroke, strokeWidth }}
                     onClick={this.onClickLine}
                     d={`M ${pt1[0]} ${pt1[1]} C ${pt1[0] + pt1norm[0]} ${pt1[1] + pt1norm[1]}, ${pt2[0] + pt2norm[0]} ${pt2[1] + pt2norm[1]}, ${pt2[0]} ${pt2[1]}`}
-                    markerEnd={link.layout_linkDisplayArrow ? `url(#${link[Id] + 'arrowhead'})` : ''}
+                    markerEnd={link.link_displayArrow ? `url(#${link[Id] + 'arrowhead'})` : ''}
                 />
                 {textX === undefined || !linkDescription ? null : (
                     <text filter={`url(#${link[Id] + 'background'})`} className="collectionfreeformlinkview-linkText" x={textX} y={textY} onPointerDown={this.pointerDown}>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 811a77fa8..11151e74e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1021,7 +1021,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     @action
     zoom = (pointX: number, pointY: number, deltaY: number): void => {
-        if (this.Document._isGroup) return;
+        if (this.Document._isGroup || this.Document._freeform_noZoom) return;
         let deltaScale = deltaY > 0 ? 1 / 1.05 : 1.05;
         if (deltaScale < 0) deltaScale = -deltaScale;
         const [x, y] = this.getTransform().transformPoint(pointX, pointY);
@@ -1294,8 +1294,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 ScreenToLocalTransform={childLayout.z ? this.getContainerTransform : this.getTransform}
                 PanelWidth={childLayout[Width]}
                 PanelHeight={childLayout[Height]}
-                docFilters={this.childDocFilters}
-                docRangeFilters={this.childDocRangeFilters}
+                childFilters={this.childDocFilters}
+                childFiltersByRanges={this.childDocRangeFilters}
                 searchFilterDocs={this.searchFilterDocs}
                 isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive}
                 isContentActive={this.props.childContentsActive ?? emptyFunction}
@@ -1317,7 +1317,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 dontRegisterView={this.props.dontRenderDocuments || this.props.dontRegisterView}
                 pointerEvents={this.pointerEvents}
                 //rotation={this.props.styleProvider?.(childLayout, this.props, StyleProp.JitterRotation) || 0}
-                //fitContentsToBox={this.props.fitContentsToBox || BoolCast(this.props.freezeChildDimensions)} // bcz: check this
+                //fitContentsToBox={this.props.fitContentsToBox || BoolCast(this.props.treeViewFreezeChildDimensions)} // bcz: check this
             />
         );
     }
@@ -1692,7 +1692,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         if ((e as any).handlePan || this.props.isAnnotationOverlay) return;
         (e as any).handlePan = true;
 
-        if (!this.layoutDoc._noAutoscroll && !this.props.renderDepth && this._marqueeRef) {
+        if (!this.layoutDoc._freeform_noAutoPan && !this.props.renderDepth && this._marqueeRef) {
             const dragX = e.detail.clientX;
             const dragY = e.detail.clientY;
             const bounds = this._marqueeRef?.getBoundingClientRect();
@@ -2007,8 +2007,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                             onKey={this.onKeyDown}
                             onDoubleClick={this.onChildDoubleClickHandler}
                             onBrowseClick={this.onBrowseClickHandler}
-                            docFilters={this.childDocFilters}
-                            docRangeFilters={this.childDocRangeFilters}
+                            childFilters={this.childDocFilters}
+                            childFiltersByRanges={this.childDocRangeFilters}
                             searchFilterDocs={this.searchFilterDocs}
                             isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive}
                             isContentActive={this.props.childContentsActive ?? emptyFunction}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index d50c8d2c4..534275610 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -522,7 +522,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             followLinkToggle: true,
             _width: 200,
             _height: 200,
-            _layoutFitContentsToBox: true,
+            _layout_fitContentsToBox: true,
             _layout_showSidebar: true,
             title: 'overview',
         });
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index ca6b8705d..65578f214 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -24,7 +24,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 linearViewExpandable 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)
@@ -46,7 +46,7 @@ export class CollectionLinearView extends CollectionSubView() {
 
     componentDidMount() {
         this._widthDisposer = reaction(
-            () => 5 + NumCast(this.rootDoc.linearBtnWidth, this.dimension()) + (this.layoutDoc.linearViewIsExpanded ? this.childDocs.filter(doc => !doc.hidden).reduce((tot, doc) => (doc[Width]() || this.dimension()) + tot + 4, 0) : 0),
+            () => 5 + NumCast(this.rootDoc.linearBtnWidth, this.dimension()) + (this.layoutDoc.linearView_IsExpanded ? this.childDocs.filter(doc => !doc.hidden).reduce((tot, doc) => (doc[Width]() || this.dimension()) + tot + 4, 0) : 0),
             width => this.childDocs.length && (this.layoutDoc._width = width),
             { fireImmediately: true }
         );
@@ -214,8 +214,8 @@ export class CollectionLinearView extends CollectionSubView() {
                     docViewPath={returnEmptyDoclist}
                     whenChildContentsActiveChanged={emptyFunction}
                     bringToFront={emptyFunction}
-                    docFilters={this.props.docFilters}
-                    docRangeFilters={this.props.docRangeFilters}
+                    childFilters={this.props.childFilters}
+                    childFiltersByRanges={this.props.childFiltersByRanges}
                     searchFilterDocs={this.props.searchFilterDocs}
                     hideResizeHandles={true}
                 />
@@ -226,7 +226,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.linearViewIsExpanded);
+        const isExpanded = BoolCast(this.layoutDoc.linearView_IsExpanded);
 
         const menuOpener = (
             <label
@@ -239,9 +239,9 @@ export class CollectionLinearView extends CollectionSubView() {
         );
 
         return (
-            <div className={`collectionLinearView-outer ${this.layoutDoc.linearViewSubMenu}`} style={{ backgroundColor: this.layoutDoc.linearViewIsExpanded ? undefined : 'transparent' }}>
+            <div className={`collectionLinearView-outer ${this.layoutDoc.linearView_SubMenu}`} style={{ backgroundColor: this.layoutDoc.linearView_IsExpanded ? undefined : 'transparent' }}>
                 <div className="collectionLinearView" ref={this.createDashEventsTarget} onContextMenu={this.myContextMenu} style={{ minHeight: this.dimension() }}>
-                    {!this.props.Document.linearViewExpandable ? null : (
+                    {!this.props.Document.linearView_Expandable ? null : (
                         <Tooltip title={<div className="dash-tooltip">{isExpanded ? 'Close' : 'Open'}</div>} placement="top">
                             {menuOpener}
                         </Tooltip>
@@ -259,11 +259,11 @@ export class CollectionLinearView extends CollectionSubView() {
                                 scriptContext: this.props.scriptContext,
                                 documentView: this.props.DocumentView?.(),
                             });
-                            this.layoutDoc.linearViewIsExpanded = this.addMenuToggle.current!.checked;
+                            this.layoutDoc.linearView_IsExpanded = this.addMenuToggle.current!.checked;
                         })}
                     />
 
-                    {!this.layoutDoc.linearViewIsExpanded ? null : (
+                    {!this.layoutDoc.linearView_IsExpanded ? null : (
                         <div
                             className="collectionLinearView-content"
                             style={{
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 78d3d1b6e..34fa0343d 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -259,8 +259,8 @@ export class CollectionMulticolumnView extends CollectionSubView() {
                 hideDecorationTitle={this.props.childHideDecorationTitle?.()}
                 fitContentsToBox={this.props.fitContentsToBox}
                 focus={this.props.focus}
-                docFilters={this.childDocFilters}
-                docRangeFilters={this.childDocRangeFilters}
+                childFilters={this.childDocFilters}
+                childFiltersByRanges={this.childDocRangeFilters}
                 searchFilterDocs={this.searchFilterDocs}
                 dontRegisterView={this.props.dontRegisterView}
                 addDocument={this.props.addDocument}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 4d61dc272..bab77ed48 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -258,8 +258,8 @@ export class CollectionMultirowView extends CollectionSubView() {
                 hideDecorationTitle={this.props.childHideDecorationTitle?.()}
                 fitContentsToBox={this.props.fitContentsToBox}
                 focus={this.props.focus}
-                docFilters={this.childDocFilters}
-                docRangeFilters={this.childDocRangeFilters}
+                childFilters={this.childDocFilters}
+                childFiltersByRanges={this.childDocRangeFilters}
                 searchFilterDocs={this.searchFilterDocs}
                 dontRegisterView={this.props.dontRegisterView}
                 addDocument={this.props.addDocument}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
index 3a0c2c85c..52ebb7763 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
@@ -104,7 +104,7 @@
 
 .schema-header-row {
     cursor: grab;
-    justify-content: flex-end;
+    //justify-content: flex-end;
 
     .row-menu {
         display: flex;
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 36abad87e..15424de98 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -32,6 +32,7 @@ export enum ColumnType {
     Date,
     Image,
     RTF,
+    Enumeration,
     Any,
 }
 
@@ -42,6 +43,7 @@ export const FInfotoColType: { [key: string]: ColumnType } = {
     date: ColumnType.Date,
     image: ColumnType.Image,
     rtf: ColumnType.RTF,
+    enumeration: ColumnType.Enumeration,
 };
 
 const defaultColumnKeys: string[] = ['title', 'type', 'author', 'author_date', 'text'];
@@ -54,6 +56,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     private _makeNewColumn: boolean = false;
     private _documentOptions: DocumentOptions = new DocumentOptions();
     private _tableContentRef: HTMLDivElement | null = null;
+    private _menuTarget = React.createRef<HTMLDivElement>();
 
     static _rowHeight: number = 50;
     static _rowSingleLineHeight: number = 32;
@@ -77,6 +80,11 @@ export class CollectionSchemaView extends CollectionSubView() {
     @observable _filterSearchValue: string = '';
     @observable _selectedCell: [Doc, number] | undefined;
 
+    // target HTMLelement portal for showing a popup menu to edit cell values.
+    public get MenuTarget() {
+        return this._menuTarget.current;
+    }
+
     @computed get _selectedDocs() {
         return SelectionManager.Docs().filter(doc => Doc.AreProtosEqual(DocCast(doc.embedContainer), this.rootDoc));
     }
@@ -336,9 +344,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     dragColumn = (e: PointerEvent, index: number) => {
         const dragData = new DragManager.ColumnDragData(index);
         const dragEles = [this._colEles[index]];
-        this.childDocs.forEach(doc => {
-            dragEles.push(this._rowEles.get(doc).children[1].children[index]);
-        });
+        this.childDocs.forEach(doc => dragEles.push(this._rowEles.get(doc).children[1].children[index]));
         DragManager.StartColumnDrag(dragEles, dragData, e.x, e.y);
 
         document.removeEventListener('pointermove', this.highlightDropColumn);
@@ -352,24 +358,28 @@ export class CollectionSchemaView extends CollectionSubView() {
         return true;
     };
 
-    @action
-    highlightDropColumn = (e: PointerEvent) => {
-        e.stopPropagation();
-        const mouseX = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY)[0];
+    findDropIndex = (mouseX: number) => {
         let index: number | undefined;
         this.displayColumnWidths.reduce((total, curr, i) => {
             if (total <= mouseX && total + curr >= mouseX) {
-                if (mouseX <= total + curr / 2) index = i;
+                if (mouseX <= total + curr) index = i;
                 else index = i + 1;
             }
             return total + curr;
         }, CollectionSchemaView._rowMenuWidth);
+        return index;
+    };
 
+    @action
+    highlightDropColumn = (e: PointerEvent) => {
+        e.stopPropagation();
+        const mouseX = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY)[0];
+        const index = this.findDropIndex(mouseX);
         this._colEles.forEach((colRef, i) => {
             let leftStyle = '';
             let rightStyle = '';
-            if (i + 1 === index) rightStyle = `solid 2px ${Colors.MEDIUM_BLUE}`;
-            if (i === index && i === 0) leftStyle = `solid 2px ${Colors.MEDIUM_BLUE}`;
+            if (i + 1 === index) rightStyle = `solid 12px ${Colors.MEDIUM_BLUE}`;
+            if (i === index && i === 0) leftStyle = `solid 12px ${Colors.MEDIUM_BLUE}`;
             colRef.style.borderLeft = leftStyle;
             colRef.style.borderRight = rightStyle;
             this.childDocs.forEach(doc => {
@@ -426,15 +436,8 @@ export class CollectionSchemaView extends CollectionSubView() {
         if (de.complete.columnDragData) {
             e.stopPropagation();
             const mouseX = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y)[0];
-            let index = de.complete.columnDragData.colIndex;
-            this.displayColumnWidths.reduce((total, curr, i) => {
-                if (total <= mouseX && total + curr >= mouseX) {
-                    if (mouseX <= total + curr / 2) index = i;
-                    else index = i + 1;
-                }
-                return total + curr;
-            }, CollectionSchemaView._rowMenuWidth);
-            this.moveColumn(de.complete.columnDragData.colIndex, index);
+            const index = this.findDropIndex(mouseX);
+            this.moveColumn(de.complete.columnDragData.colIndex, index ?? de.complete.columnDragData.colIndex);
 
             this._colEles.forEach((colRef, i) => {
                 colRef.style.borderLeft = '';
@@ -607,7 +610,7 @@ export class CollectionSchemaView extends CollectionSubView() {
         this._menuKeys = this.documentKeys.filter(value => value.toLowerCase().includes(this._menuValue.toLowerCase()));
     };
 
-    getFieldFilters = (field: string) => StrListCast(this.Document._docFilters).filter(filter => filter.split(':')[0] == field);
+    getFieldFilters = (field: string) => StrListCast(this.Document._childFilters).filter(filter => filter.split(':')[0] == field);
 
     removeFieldFilters = (field: string) => {
         this.getFieldFilters(field).forEach(filter => Doc.setDocFilter(this.Document, field, filter.split(':')[1], 'remove'));
@@ -760,7 +763,7 @@ export class CollectionSchemaView extends CollectionSubView() {
             }
         });
 
-        const filters = StrListCast(this.Document._docFilters);
+        const filters = StrListCast(this.Document._childFilters);
         return keyOptions.map(key => {
             let bool = false;
             if (filters !== undefined) {
@@ -832,6 +835,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     render() {
         return (
             <div className="collectionSchemaView" ref={(ele: HTMLDivElement | null) => this.createDashEventsTarget(ele)} onDrop={this.onExternalDrop.bind(this)}>
+                <div ref={this._menuTarget} style={{ background: 'red', top: 0, left: 0, position: 'absolute', zIndex: 10000 }}></div>
                 <div
                     className="schema-table"
                     onWheel={e => this.props.isContentActive() && e.stopPropagation()}
@@ -860,6 +864,7 @@ export class CollectionSchemaView extends CollectionSubView() {
                                 openContextMenu={this.openContextMenu}
                                 dragColumn={this.dragColumn}
                                 setColRef={this.setColRef}
+                                isContentActive={this.props.isContentActive}
                             />
                         ))}
                     </div>
@@ -894,8 +899,8 @@ export class CollectionSchemaView extends CollectionSubView() {
                                 isContentActive={returnTrue}
                                 isDocumentActive={returnFalse}
                                 ScreenToLocalTransform={this.screenToLocal}
-                                docFilters={this.childDocFilters}
-                                docRangeFilters={this.childDocRangeFilters}
+                                childFilters={this.childDocFilters}
+                                childFiltersByRanges={this.childDocRangeFilters}
                                 searchFilterDocs={this.searchFilterDocs}
                                 styleProvider={DefaultStyleProvider}
                                 docViewPath={returnEmptyDoclist}
@@ -940,6 +945,7 @@ class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsP
                                 LayoutTemplateString={SchemaRowBox.LayoutString(this.props.schema.props.fieldKey)}
                                 Document={doc}
                                 DataDoc={dataDoc}
+                                yPadding={index}
                                 renderDepth={this.props.schema.props.renderDepth + 1}
                                 PanelWidth={this.tableWidthFunc}
                                 PanelHeight={this.props.rowHeight}
@@ -949,8 +955,8 @@ class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsP
                                 enableDragWhenActive={true}
                                 onClickScriptDisable="always"
                                 focus={this.props.schema.focusDocument}
-                                docFilters={this.props.schema.childDocFilters}
-                                docRangeFilters={this.props.schema.childDocRangeFilters}
+                                childFilters={this.props.schema.childDocFilters}
+                                childFiltersByRanges={this.props.schema.childDocRangeFilters}
                                 searchFilterDocs={this.props.schema.searchFilterDocs}
                                 rootSelected={this.props.schema.rootSelected}
                                 ScreenToLocalTransform={this.childScreenToLocal(index)}
diff --git a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
index 46c2f622b..65e47f441 100644
--- a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
@@ -12,6 +12,7 @@ export interface SchemaColumnHeaderProps {
     columnIndex: number;
     sortField: string;
     sortDesc: boolean;
+    isContentActive: (outsideReaction?: boolean | undefined) => boolean | undefined;
     setSort: (field: string | undefined, desc?: boolean) => void;
     removeColumn: (index: number) => void;
     rowHeight: () => number;
@@ -44,7 +45,7 @@ export class SchemaColumnHeader extends React.Component<SchemaColumnHeaderProps>
 
     @action
     onPointerDown = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, e => this.props.dragColumn(e, this.props.columnIndex), emptyFunction, emptyFunction, false);
+        this.props.isContentActive(true) && setupMoveUpEvents(this, e, e => this.props.dragColumn(e, this.props.columnIndex), emptyFunction, emptyFunction, false);
     };
 
     render() {
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index 978b65053..4f3503751 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -7,7 +7,7 @@ import { Doc } from '../../../../fields/Doc';
 import { BoolCast } from '../../../../fields/Types';
 import { DragManager } from '../../../util/DragManager';
 import { SnappingManager } from '../../../util/SnappingManager';
-import { undoable, undoBatch } from '../../../util/UndoManager';
+import { undoable } from '../../../util/UndoManager';
 import { ViewBoxBaseComponent } from '../../DocComponent';
 import { Colors } from '../../global/globalEnums';
 import { OpenWhere } from '../../nodes/DocumentView';
@@ -15,6 +15,7 @@ import { FieldView, FieldViewProps } from '../../nodes/FieldView';
 import { CollectionSchemaView } from './CollectionSchemaView';
 import './CollectionSchemaView.scss';
 import { SchemaTableCell } from './SchemaTableCell';
+import { Transform } from '../../../util/Transform';
 
 @observer
 export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -143,6 +144,13 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             selectedCell={this.selectedCell}
                             setColumnValues={this.setColumnValues}
                             oneLine={BoolCast(this.schemaDoc?._singleLine)}
+                            menuTarget={this.schemaView.MenuTarget}
+                            transform={() => {
+                                const ind = index === this.schemaView.columnKeys.length - 1 ? this.schemaView.columnKeys.length - 3 : index;
+                                const x = this.schemaView?.displayColumnWidths.reduce((p, c, i) => (i <= ind ? p + c : p), 0);
+                                const y = (this.props.yPadding ?? 0) * this.props.PanelHeight();
+                                return new Transform(x + CollectionSchemaView._rowMenuWidth, y, 1);
+                            }}
                         />
                     ))}
                 </div>
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 876693474..97264508c 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -1,4 +1,5 @@
-import React = require('react');
+import * as React from 'react';
+import Select, { MenuPlacement } from 'react-select';
 import { action, computed, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import { extname } from 'path';
@@ -6,12 +7,11 @@ import DatePicker from 'react-datepicker';
 import { DateField } from '../../../../fields/DateField';
 import { Doc, DocListCast, Field } from '../../../../fields/Doc';
 import { RichTextField } from '../../../../fields/RichTextField';
-import { BoolCast, Cast, DateCast, DocCast, FieldValue } from '../../../../fields/Types';
+import { BoolCast, Cast, DateCast, DocCast, FieldValue, StrCast } from '../../../../fields/Types';
 import { ImageField } from '../../../../fields/URLField';
 import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero, Utils } from '../../../../Utils';
 import { FInfo } from '../../../documents/Documents';
 import { DocFocusOrOpen } from '../../../util/DocumentManager';
-import { dropActionType } from '../../../util/DragManager';
 import { Transform } from '../../../util/Transform';
 import { undoable, undoBatch } from '../../../util/UndoManager';
 import { EditableView } from '../../EditableView';
@@ -21,7 +21,7 @@ import { FieldView, FieldViewProps } from '../../nodes/FieldView';
 import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
 import { KeyValueBox } from '../../nodes/KeyValueBox';
 import { DefaultStyleProvider } from '../../StyleProvider';
-import { ColumnType, FInfotoColType } from './CollectionSchemaView';
+import { CollectionSchemaView, ColumnType, FInfotoColType } from './CollectionSchemaView';
 import './CollectionSchemaView.scss';
 
 export interface SchemaTableCellProps {
@@ -41,6 +41,9 @@ export interface SchemaTableCellProps {
     oneLine?: boolean; // whether all input should fit on one line vs allowing textare multiline inputs
     allowCRs?: boolean; // allow carriage returns in text input (othewrise CR ends the edit)
     finishEdit?: () => void; // notify container that edit is over (eg. to hide view in DashFieldView)
+    options?: string[];
+    menuTarget: HTMLDivElement | null;
+    transform: () => Transform;
 }
 
 @observer
@@ -64,8 +67,8 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
         const color = protoCount === 0 || (fieldKey.startsWith('_') && Document[fieldKey] === undefined) ? 'black' : 'blue';
         const textDecoration = color !== 'black' && parenCount ? 'underline' : '';
         const fieldProps: FieldViewProps = {
-            docFilters: returnEmptyFilter,
-            docRangeFilters: returnEmptyFilter,
+            childFilters: returnEmptyFilter,
+            childFiltersByRanges: returnEmptyFilter,
             searchFilterDocs: returnEmptyDoclist,
             styleProvider: DefaultStyleProvider,
             docViewPath: returnEmptyDoclist,
@@ -129,15 +132,15 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
     }
 
     get getCellType() {
+        const columnTypeStr = this.props.getFinfo(this.props.fieldKey)?.fieldType;
         const cellValue = this.props.Document[this.props.fieldKey];
         if (cellValue instanceof ImageField) return ColumnType.Image;
         if (cellValue instanceof DateField) return ColumnType.Date;
         if (cellValue instanceof RichTextField) return ColumnType.RTF;
         if (typeof cellValue === 'number') return ColumnType.Any;
-        if (typeof cellValue === 'string') return ColumnType.Any;
+        if (typeof cellValue === 'string' && columnTypeStr !== 'enumeration') return ColumnType.Any;
         if (typeof cellValue === 'boolean') return ColumnType.Boolean;
 
-        const columnTypeStr = this.props.getFinfo(this.props.fieldKey)?.fieldType;
         if (columnTypeStr && columnTypeStr in FInfotoColType) {
             return FInfotoColType[columnTypeStr];
         }
@@ -152,6 +155,7 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
             case ColumnType.Image: return <SchemaImageCell {...this.props} />;
             case ColumnType.Boolean: return <SchemaBoolCell {...this.props} />;
             case ColumnType.RTF: return <SchemaRTFCell {...this.props} />;
+            case ColumnType.Enumeration: return <SchemaEnumerationCell {...this.props} options={this.props.getFinfo(this.props.fieldKey)?.values?.map(val => val.toString())} />;
             case ColumnType.Date: // return <SchemaDateCell {...this.props} />;
             default:  return this.defaultCellContent;
         }
@@ -311,3 +315,38 @@ export class SchemaBoolCell extends React.Component<SchemaTableCellProps> {
         );
     }
 }
+@observer
+export class SchemaEnumerationCell extends React.Component<SchemaTableCellProps> {
+    @computed get selected() {
+        const selected: [Doc, number] | undefined = this.props.selectedCell();
+        return this.props.isRowActive() && selected?.[0] === this.props.Document && selected[1] === this.props.col;
+    }
+    render() {
+        const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this.props);
+        const options = this.props.options?.map(facet => ({ value: facet, label: facet }));
+        return (
+            <div className="schemaSelectionCell" style={{ display: 'flex', color, textDecoration, cursor, pointerEvents }}>
+                <div style={{ width: '100%' }}>
+                    <Select
+                        styles={{
+                            menuPortal: base => ({
+                                ...base,
+                                left: 0,
+                                top: 0,
+                                transform: `translate(${this.props.transform().TranslateX}px, ${this.props.transform().TranslateY}px)`,
+                                width: Number(base.width) * this.props.transform().Scale,
+                                zIndex: 9999,
+                            }),
+                        }}
+                        menuPortalTarget={this.props.menuTarget}
+                        menuPosition={'absolute'}
+                        placeholder={StrCast(this.props.Document[this.props.fieldKey], 'select...')}
+                        options={options}
+                        isMulti={false}
+                        onChange={val => KeyValueBox.SetField(this.props.Document, this.props.fieldKey.replace(/^_/, ''), `"${val?.value ?? ''}"`)}
+                    />
+                </div>
+            </div>
+        );
+    }
+}
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index b9d56541a..8324a97d9 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -25,7 +25,7 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
 
     getBackgroundColor = (): string => {
         const link_relationshipList = StrListCast(Doc.UserDoc().link_relationshipList);
-        const linkColorList = StrListCast(Doc.UserDoc().linkColorList);
+        const linkColorList = StrListCast(Doc.UserDoc().link_ColorList);
         let color = 'white';
         // if this link's relationship property is not default "link", set its color
         if (link_relationshipList) {
diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx
index 5f2d4a7b6..664a0fa4f 100644
--- a/src/client/views/linking/LinkPopup.tsx
+++ b/src/client/views/linking/LinkPopup.tsx
@@ -89,8 +89,8 @@ export class LinkPopup extends React.Component<LinkPopupProps> {
                         docViewPath={returnEmptyDoclist}
                         whenChildContentsActiveChanged={emptyFunction}
                         bringToFront={emptyFunction}
-                        docFilters={returnEmptyFilter}
-                        docRangeFilters={returnEmptyFilter}
+                        childFilters={returnEmptyFilter}
+                        childFiltersByRanges={returnEmptyFilter}
                         searchFilterDocs={returnEmptyDoclist}
                     />
                 </div>
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index 60480ac33..d84e34d52 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -57,7 +57,7 @@ export class TableBox extends React.Component<TableBoxProps> {
                                                             DragManager.StartAnchorAnnoDrag([header.current!], new DragManager.AnchorAnnoDragData(this.props.docView()!, sourceAnchorCreator, targetCreator), downX, downY, {
                                                                 dragComplete: e => {
                                                                     if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
-                                                                        e.linkDocument.layout_linkDisplay = true;
+                                                                        e.linkDocument.link_displayLine = true;
                                                                         // e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.props.rootDoc;
                                                                         // e.annoDragData.linkSourceDoc.followLinkZoom = false;
                                                                     }
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index bd1952ecb..7723a088d 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -71,7 +71,9 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
             e,
             this.onLinkButtonMoved,
             emptyFunction,
-            action((e, doubleTap) => doubleTap && DocumentView.showBackLinks(this.props.View.rootDoc)),
+            action((e, doubleTap) => {
+                doubleTap && DocumentView.showBackLinks(this.props.View.rootDoc);
+            }),
             undefined,
             undefined,
             action(() => (DocumentLinksButton.LinkEditorDocView = this.props.View))
@@ -191,7 +193,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
 
     @computed get filteredLinks() {
         const results = [] as Doc[];
-        const filters = this.props.View.props.docFilters();
+        const filters = this.props.View.props.childFilters();
         Array.from(new Set<Doc>(this.props.View.allLinks)).forEach(link => {
             if (DocUtils.FilterDocs([link], filters, []).length || DocUtils.FilterDocs([link.link_anchor_2 as Doc], filters, []).length || DocUtils.FilterDocs([link.link_anchor_1 as Doc], filters, []).length) {
                 results.push(link);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 687683e6e..461066b1b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -168,8 +168,8 @@ export interface DocumentViewSharedProps {
     setTitleFocus?: () => void;
     focus: DocFocusFunc;
     layout_fitWidth?: (doc: Doc) => boolean | undefined;
-    docFilters: () => string[];
-    docRangeFilters: () => string[];
+    childFilters: () => string[];
+    childFiltersByRanges: () => string[];
     searchFilterDocs: () => Doc[];
     layout_showTitle?: () => string;
     whenChildContentsActiveChanged: (isActive: boolean) => void;
@@ -192,7 +192,7 @@ export interface DocumentViewSharedProps {
     hideCaptions?: boolean;
     ignoreAutoHeight?: boolean;
     forceAutoHeight?: boolean;
-    disableDocBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over.
+    disableBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over.
     onClickScriptDisable?: 'never' | 'always'; // undefined = only when selected
     enableDragWhenActive?: boolean;
     waitForDoubleClickToClick?: () => 'never' | 'always' | undefined;
@@ -664,7 +664,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
     @action
     onContextMenu = (e?: React.MouseEvent, pageX?: number, pageY?: number) => {
-        if (e && this.rootDoc._hideContextMenu && Doc.noviceMode) {
+        if (e && this.rootDoc._layout_hideContextMenu && Doc.noviceMode) {
             e.preventDefault();
             e.stopPropagation();
             //!this.props.isSelected(true) && SelectionManager.SelectView(this.props.DocumentView(), false);
@@ -880,7 +880,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             ? true
             : false;
     };
-    docFilters = () => [...this.props.docFilters(), ...StrListCast(this.layoutDoc.docFilters)];
+    childFilters = () => [...this.props.childFilters(), ...StrListCast(this.layoutDoc.childFilters)];
     contentPointerEvents = () => (!this.disableClickScriptFunc && this.onClickHandler ? 'none' : this.pointerEvents);
     @computed get contents() {
         TraceMobx();
@@ -912,7 +912,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     docViewPath={this.props.viewPath}
                     thumbShown={this.thumbShown}
                     setContentView={this.setContentView}
-                    docFilters={this.docFilters}
+                    childFilters={this.childFilters}
                     NativeDimScaling={this.props.NativeDimScaling}
                     PanelHeight={this.panelHeight}
                     setHeight={!this.props.suppressSetHeight ? this.setHeight : undefined}
@@ -924,7 +924,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     setTitleFocus={this.setTitleFocus}
                     layout_fieldKey={this.finalLayoutKey}
                 />
-                {this.layoutDoc.hideAllLinks ? null : this.allLinkEndpoints}
+                {this.layoutDoc.layout_hideAllLinks ? null : this.allLinkEndpoints}
             </div>
         );
     }
@@ -960,12 +960,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         TraceMobx();
         return LinkManager.Instance.getAllRelatedLinks(this.rootDoc);
     }
-    hideLink = computedFn((link: Doc) => () => (link.layout_linkDisplay = false));
+    hideLink = computedFn((link: Doc) => () => (link.link_displayLine = false));
     @computed get allLinkEndpoints() {
         // the small blue dots that mark the endpoints of links
         TraceMobx();
         if (this.props.hideLinkAnchors || this.layoutDoc.layout_hideLinkAnchors || this.props.dontRegisterView || this.layoutDoc.layout_unrendered) return null;
-        const filtered = DocUtils.FilterDocs(this.directLinks, this.props.docFilters?.() ?? [], []).filter(d => d.layout_linkDisplay);
+        const filtered = DocUtils.FilterDocs(this.directLinks, this.props.childFilters?.() ?? [], []).filter(d => d.link_displayLine);
         return filtered.map(link => (
             <div className="documentView-anchorCont" key={link[Id]}>
                 <DocumentView
@@ -1013,7 +1013,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 gumStream = stream;
                 recorder = new MediaRecorder(stream);
                 recorder.ondataavailable = async (e: any) => {
-                    const [{ result }] = await Networking.UploadFilesToServer({file: e.data});
+                    const [{ result }] = await Networking.UploadFilesToServer({ file: e.data });
                     if (!(result instanceof Error)) {
                         const audioField = new AudioField(result.accessPaths.agnostic.client);
                         const audioAnnos = Cast(dataDoc[field + '-audioAnnotations'], listSpec(AudioField), null);
@@ -1296,13 +1296,13 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     }
 
     // shows a stacking view collection (by default, but the user can change) of all documents linked to the source
-    public static showBackLinks(linkSource: Doc) {
-        const docId = Doc.CurrentUserEmail + Doc.GetProto(linkSource)[Id] + '-pivotish';
+    public static showBackLinks(linkAnchor: Doc) {
+        const docId = Doc.CurrentUserEmail + Doc.GetProto(linkAnchor)[Id] + '-pivotish';
         // prettier-ignore
-        DocServer.GetRefField(docId).then(docx => docx instanceof Doc &&
+        DocServer.GetRefField(docId).then(docx =>
             LightboxView.SetLightboxDoc(
-                docx || // reuse existing pivot view of documents, or else create a new collection
-                Docs.Create.StackingDocument([], { title: linkSource.title + '-pivot', _width: 500, _height: 500, linkSource, updateContentsScript: ScriptField.MakeScript('updateLinkCollection(self)') }, docId)
+                (docx as Doc) ?? // reuse existing pivot view of documents, or else create a new collection
+                Docs.Create.StackingDocument([], { title: linkAnchor.title + '-pivot', _width: 500, _height: 500, target: linkAnchor, updateContentsScript: ScriptField.MakeScript('updateLinkCollection(self, self.target)') }, docId)
             )
         );
     }
@@ -1490,8 +1490,8 @@ export class DocumentView extends React.Component<DocumentViewProps> {
             // increase max auto height if document has been resized to be greater than current max
             () => NumCast(this.layoutDoc._height),
             action(height => {
-                const docMax = NumCast(this.layoutDoc.docMaxAutoHeight);
-                if (docMax && docMax < height) this.layoutDoc.docMaxAutoHeight = height;
+                const docMax = NumCast(this.layoutDoc.layout_maxAutoHeight);
+                if (docMax && docMax < height) this.layoutDoc.layout_maxAutoHeight = height;
             })
         );
         !BoolCast(this.props.Document.dontRegisterView, this.props.dontRegisterView) && DocumentManager.Instance.AddView(this);
@@ -1577,16 +1577,16 @@ ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuff
     else dv.switchViews(true, detailLayoutKeySuffix, undefined, true);
 });
 
-ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc) {
-    const linkSource = Cast(linkCollection.linkSource, Doc, null);
+ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSource: Doc) {
     const collectedLinks = DocListCast(Doc.GetProto(linkCollection).data);
     let wid = linkSource[Width]();
+    let embedding:Doc|undefined;
     const links = LinkManager.Links(linkSource);
     links.forEach(link => {
         const other = LinkManager.getOppositeAnchor(link, linkSource);
-        const otherdoc = !other ? undefined : other.annotationOn ? Cast(other.annotationOn, Doc, null) : other;
+        const otherdoc = DocCast(other?.annotationOn ?? other);
         if (otherdoc && !collectedLinks?.some(d => Doc.AreProtosEqual(d, otherdoc))) {
-            const embedding = Doc.MakeEmbedding(otherdoc);
+            embedding = Doc.MakeEmbedding(otherdoc);
             embedding.x = wid;
             embedding.y = 0;
             embedding._lockedPosition = false;
@@ -1594,5 +1594,6 @@ ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc) {
             Doc.AddDocToList(Doc.GetProto(linkCollection), 'data', embedding);
         }
     });
+    embedding && DocServer.UPDATE_SERVER_CACHE();// if a new embedding was made, update the client's server cache so that it will not come back as a promise
     return links;
 });
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 4e348c67c..64f25cb22 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -57,8 +57,8 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
         const props: FieldViewProps = {
             Document: this.props.doc,
             DataDoc: this.props.doc,
-            docFilters: returnEmptyFilter,
-            docRangeFilters: returnEmptyFilter,
+            childFilters: returnEmptyFilter,
+            childFiltersByRanges: returnEmptyFilter,
             searchFilterDocs: returnEmptyDoclist,
             styleProvider: DefaultStyleProvider,
             docViewPath: returnEmptyDoclist,
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index 73823041c..52f3575cb 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -87,8 +87,8 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProp
         const params = {
             rotateText: null,
             fontSizeFactor: 1,
-            minimumFontSize: NumCast(this.rootDoc._minFontSize, 8),
-            maximumFontSize: NumCast(this.rootDoc._maxFontSize, 1000),
+            minimumFontSize: NumCast(this.rootDoc._label_minFontSize, 8),
+            maximumFontSize: NumCast(this.rootDoc._label_maxFontSize, 1000),
             limitingDimension: 'both',
             horizontalAlign: 'center',
             verticalAlign: 'center',
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index f38ef634c..e86b881a8 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -53,7 +53,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
             } else {
                 this.rootDoc[this.fieldKey + '_x'] = ((pt[0] - bounds.left) / bounds.width) * 100;
                 this.rootDoc[this.fieldKey + '_y'] = ((pt[1] - bounds.top) / bounds.height) * 100;
-                this.rootDoc.layout_autoMoveAnchors = false;
+                this.rootDoc.link_autoMoveAnchors = false;
             }
         }
         return false;
@@ -68,7 +68,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const y = NumCast(this.rootDoc[this.fieldKey + '_y'], 100);
         const background = this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.BackgroundColor + ':anchor');
         const anchor = this.fieldKey === 'link_anchor_1' ? 'link_anchor_2' : 'link_anchor_1';
-        const anchorScale = !this.dataDoc[this.fieldKey + '_useLinkSmallAnchor'] && (x === 0 || x === 100 || y === 0 || y === 100) ? 1 : 0.25;
+        const anchorScale = !this.dataDoc[this.fieldKey + '_useSmallAnchor'] && (x === 0 || x === 100 || y === 0 || y === 100) ? 1 : 0.25;
         const targetTitle = StrCast((this.dataDoc[anchor] as Doc)?.title);
         const selView = SelectionManager.Views().lastElement()?.props.LayoutTemplateString?.includes('link_anchor_1')
             ? 'link_anchor_1'
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 079e1b56d..86191de63 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -268,8 +268,8 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
                             addDocTab={returnFalse}
                             pinToPres={returnFalse}
                             dontRegisterView={true}
-                            docFilters={returnEmptyFilter}
-                            docRangeFilters={returnEmptyFilter}
+                            childFilters={returnEmptyFilter}
+                            childFiltersByRanges={returnEmptyFilter}
                             searchFilterDocs={returnEmptyDoclist}
                             renderDepth={0}
                             suppressSetHeight={true}
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index f03954789..93e54ffb7 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -394,7 +394,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%');
     }
     @computed get sidebarColor() {
-        return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + '_backgroundColor'], '#e4e4e4'));
+        return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this.props.fieldKey + '_backgroundColor'], '#e4e4e4'));
     }
 
     /**
@@ -550,8 +550,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth();
     panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
     scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
-    transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()];
-    opaqueFilter = () => [...this.props.docFilters(), Utils.IsOpaqueFilter()];
+    transparentFilter = () => [...this.props.childFilters(), Utils.IsTransparentFilter()];
+    opaqueFilter = () => [...this.props.childFilters(), Utils.IsOpaqueFilter()];
     infoWidth = () => this.props.PanelWidth() / 5;
     infoHeight = () => this.props.PanelHeight() / 5;
     anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
@@ -601,7 +601,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     };
     bingMapReady = (map: any) => (this._bingMap = map.map);
     render() {
-        const renderAnnotations = (docFilters?: () => string[]) => null;
+        const renderAnnotations = (childFilters?: () => string[]) => null;
         return (
             <div className="mapBox" ref={this._ref}>
                 <div
diff --git a/src/client/views/nodes/MapBox/MapBox2.tsx b/src/client/views/nodes/MapBox/MapBox2.tsx
index 18fcc951a..72f37b62c 100644
--- a/src/client/views/nodes/MapBox/MapBox2.tsx
+++ b/src/client/views/nodes/MapBox/MapBox2.tsx
@@ -391,7 +391,7 @@ export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%');
     }
     @computed get sidebarColor() {
-        return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + '_backgroundColor'], '#e4e4e4'));
+        return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this.props.fieldKey + '_backgroundColor'], '#e4e4e4'));
     }
 
     /**
@@ -547,8 +547,8 @@ export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth();
     panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
     scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
-    transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()];
-    opaqueFilter = () => [...this.props.docFilters(), Utils.IsOpaqueFilter()];
+    transparentFilter = () => [...this.props.childFilters(), Utils.IsTransparentFilter()];
+    opaqueFilter = () => [...this.props.childFilters(), Utils.IsOpaqueFilter()];
     infoWidth = () => this.props.PanelWidth() / 5;
     infoHeight = () => this.props.PanelHeight() / 5;
     anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
@@ -558,7 +558,7 @@ export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return (window as any).Microsoft.Maps;
     }
     render() {
-        const renderAnnotations = (docFilters?: () => string[]) => null;
+        const renderAnnotations = (childFilters?: () => string[]) => null;
         return (
             <div className="MapBox2" ref={this._ref}>
                 <div
diff --git a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
index 6b26f494b..577101445 100644
--- a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
+++ b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
@@ -59,7 +59,7 @@ export class MapBoxInfoWindow extends React.Component<MapBoxInfoWindowProps & Vi
                             fieldKey="data"
                             NativeWidth={returnZero}
                             NativeHeight={returnZero}
-                            docFilters={returnEmptyFilter}
+                            childFilters={returnEmptyFilter}
                             setHeight={emptyFunction}
                             isAnnotationOverlay={false}
                             select={emptyFunction}
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 6ee1b67f2..c210176b0 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -441,7 +441,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return PDFBox.sidebarResizerWidth + nativeDiff * (this.props.NativeDimScaling?.() || 1);
     };
     @undoBatch
-    toggleSidebarType = () => (this.rootDoc.sidebarViewType = this.rootDoc.sidebarViewType === CollectionViewType.Freeform ? CollectionViewType.Stacking : CollectionViewType.Freeform);
+    toggleSidebarType = () => (this.rootDoc.sidebar_collectionType = this.rootDoc.sidebar_collectionType === CollectionViewType.Freeform ? CollectionViewType.Stacking : CollectionViewType.Freeform);
     specificContextMenu = (e: React.MouseEvent): void => {
         const cm = ContextMenu.Instance;
         const options = cm.findByDescription('Options...');
@@ -557,7 +557,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         };
         return (
             <div className={'formattedTextBox-sidebar' + (Doc.ActiveTool !== InkTool.None ? '-inking' : '')} style={{ width: '100%', right: 0, backgroundColor: `white` }}>
-                {renderComponent(StrCast(this.layoutDoc.sidebarViewType))}
+                {renderComponent(StrCast(this.layoutDoc.sidebar_collectionType))}
             </div>
         );
     }
diff --git a/src/client/views/nodes/QueryBox.scss b/src/client/views/nodes/QueryBox.scss
deleted file mode 100644
index b5f90aa1e..000000000
--- a/src/client/views/nodes/QueryBox.scss
+++ /dev/null
@@ -1,5 +0,0 @@
-.queryBox, .queryBox-dragging {
-    width: 100%;
-    height: 100%; 
-    position: absolute;
-}
\ No newline at end of file
diff --git a/src/client/views/nodes/QueryBox.tsx b/src/client/views/nodes/QueryBox.tsx
deleted file mode 100644
index 1b6056be6..000000000
--- a/src/client/views/nodes/QueryBox.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-// import React = require("react");
-// import { IReactionDisposer } from "mobx";
-// import { observer } from "mobx-react";
-// import { documentSchema } from "../../../new_fields/documentSchemas";
-// import { Id } from '../../../new_fields/FieldSymbols';
-// import { makeInterface, listSpec } from "../../../new_fields/Schema";
-// import { StrCast, Cast } from "../../../new_fields/Types";
-// import { ViewBoxAnnotatableComponent } from '../DocComponent';
-// import { SearchBox } from "../search/SearchBox";
-// import { FieldView, FieldViewProps } from './FieldView';
-// import "./QueryBox.scss";
-// import { List } from "../../../new_fields/List";
-// import { SnappingManager } from "../../util/SnappingManager";
-
-// type QueryDocument = makeInterface<[typeof documentSchema]>;
-// const QueryDocument = makeInterface(documentSchema);
-
-// @observer
-// export class QueryBox extends ViewBoxAnnotatableComponent<FieldViewProps, QueryDocument>(QueryDocument) {
-//     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(QueryBox, fieldKey); }
-//     _docListChangedReaction: IReactionDisposer | undefined;
-//     componentDidMount() {
-//     }
-
-//     componentWillUnmount() {
-//         this._docListChangedReaction?.();
-//     }
-
-//     render() {
-//         const dragging = !SnappingManager.GetIsDragging() ? "" : "-dragging";
-//         return <div className={`queryBox${dragging}`} onWheel={(e) => e.stopPropagation()} >
-            
-//             <SearchBox  Document={this.props.Document}  />
-//         </div >;
-//     }
-// }
-
-// //<SearchBox id={this.props.Document[Id]} sideBar={side} Document={this.props.Document} searchQuery={StrCast(this.dataDoc.searchQuery)} filterQuery={this.dataDoc.filterQuery} />
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 34dcde23b..1f52c2d92 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -913,7 +913,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     marqueeFitScaling = () => ((this.props.NativeDimScaling?.() || 1) * this.heightPercent) / 100;
     marqueeOffset = () => [((this.panelWidth() / 2) * (1 - this.heightPercent / 100)) / (this.heightPercent / 100), 0];
 
-    timelineDocFilter = () => [`_timelineLabel:true,${Utils.noRecursionHack}:x`];
+    timelineDocFilter = () => [`_isTimelineLabel:true,${Utils.noRecursionHack}:x`];
 
     // renders video controls
     componentUI = (boundsLeft: number, boundsTop: number) => {
@@ -1082,7 +1082,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                             PanelHeight={this.panelHeight}
                             isAnyChildContentActive={returnFalse}
                             ScreenToLocalTransform={this.screenToLocalTransform}
-                            docFilters={this.timelineDocFilter}
+                            childFilters={this.timelineDocFilter}
                             select={emptyFunction}
                             focus={emptyFunction}
                             NativeDimScaling={returnOne}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 48f77469f..2ff0245d2 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -900,7 +900,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
         const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this.props.pointerEvents?.() as any);
         const scale = previewScale * (this.props.NativeDimScaling?.() || 1);
-        const renderAnnotations = (docFilters: () => string[]) => (
+        const renderAnnotations = (childFilters: () => string[]) => (
             <CollectionFreeFormView
                 {...this.props}
                 setContentView={this.setInnerContent}
@@ -919,7 +919,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 NativeDimScaling={returnOne}
                 focus={this.focus}
                 dropAction="embed"
-                docFilters={docFilters}
+                childFilters={childFilters}
                 select={emptyFunction}
                 isAnyChildContentActive={returnFalse}
                 bringToFront={emptyFunction}
@@ -995,8 +995,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
     scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
     anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
-    transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()];
-    opaqueFilter = () => [...this.props.docFilters(), Utils.noDragsDocFilter, ...(DragManager.docsBeingDragged.length ? [] : [Utils.IsOpaqueFilter()])];
+    transparentFilter = () => [...this.props.childFilters(), Utils.IsTransparentFilter()];
+    opaqueFilter = () => [...this.props.childFilters(), Utils.noDragsDocFilter, ...(DragManager.docsBeingDragged.length ? [] : [Utils.IsOpaqueFilter()])];
     childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
         if (doc instanceof Doc && property === StyleProp.PointerEvents) {
             if (this.inlineTextAnnotations.includes(doc)) return 'none';
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index 601759dbb..5bba51ec8 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -95,7 +95,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
 
     // Determining UI Specs
     @computed get label() {
-        return StrCast(this.rootDoc.label, StrCast(this.rootDoc.title));
+        return StrCast(this.rootDoc.icon_label, StrCast(this.rootDoc.title));
     }
     Icon = (color: string) => {
         const icon = StrCast(this.dataDoc[this.fieldKey ?? 'icon'] ?? this.dataDoc.icon, 'user') as any;
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index 1d41eb379..48f4c2afd 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -217,8 +217,8 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
                     whenChildContentsActiveChanged={returnFalse}
                     bringToFront={emptyFunction}
                     dontRegisterView={false}
-                    docFilters={this.props.tbox?.props.docFilters}
-                    docRangeFilters={this.props.tbox?.props.docRangeFilters}
+                    childFilters={this.props.tbox?.props.childFilters}
+                    childFiltersByRanges={this.props.tbox?.props.childFiltersByRanges}
                     searchFilterDocs={this.props.tbox?.props.searchFilterDocs}
                 />
             </div>
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 105fb9003..115777c18 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -132,7 +132,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         return this._showSidebar ? '20%' : StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%');
     }
     @computed get sidebarColor() {
-        return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.fieldKey + '_backgroundColor'], '#e4e4e4'));
+        return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this.fieldKey + '_backgroundColor'], '#e4e4e4'));
     }
     @computed get layout_autoHeight() {
         return (this.props.forceAutoHeight || this.layoutDoc._layout_autoHeight) && !this.props.ignoreAutoHeight;
@@ -811,9 +811,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             icon: !this.Document._layout_noSidebar ? 'eye-slash' : 'eye',
         });
         uicontrols.push({
-            description: (this.Document._layout_altContentUI ? 'Hide' : 'Show') + ' Alt Content UI',
-            event: () => (this.layoutDoc._layout_altContentUI = !this.layoutDoc._layout_altContentUI),
-            icon: !this.Document._layout_altContentUI ? 'eye-slash' : 'eye',
+            description: (this.Document._layout_enableAltContentUI ? 'Hide' : 'Show') + ' Alt Content UI',
+            event: () => (this.layoutDoc._layout_enableAltContentUI = !this.layoutDoc._layout_enableAltContentUI),
+            icon: !this.Document._layout_enableAltContentUI ? 'eye-slash' : 'eye',
         });
         uicontrols.push({ description: 'Show Highlights...', noexpand: true, subitems: highlighting, icon: 'hand-point-right' });
         !Doc.noviceMode &&
@@ -1741,7 +1741,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
 
         const state = this._editorView!.state;
         const curText = state.doc.textBetween(0, state.doc.content.size, ' \n');
-        if (this.layoutDoc.sidebarViewType === 'translation' && !this.fieldKey.includes('translation') && curText.endsWith(' ') && curText !== this._lastText) {
+        if (this.layoutDoc.sidebar_collectionType === 'translation' && !this.fieldKey.includes('translation') && curText.endsWith(' ') && curText !== this._lastText) {
             try {
                 translateGoogleApi(curText, { from: 'en', to: 'es' }).then((result1: any) => {
                     setTimeout(
@@ -1837,7 +1837,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 return toNum(height) + Math.max(0, toNum(marginTop)) + Math.max(0, toNum(marginBottom));
             };
             const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + toHgt(child), margins);
-            const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight);
+            const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.layout_maxAutoHeight, proseHeight), proseHeight);
             if (this.props.setHeight && scrollHeight && !this.props.dontRegisterView) {
                 // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
                 const setScrollHeight = () => (this.rootDoc[this.fieldKey + '_scrollHeight'] = scrollHeight);
@@ -1949,19 +1949,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                         fitContentsToBox={this.fitContentsToBox}
                         noSidebar={true}
                         treeViewHideTitle={true}
-                        fieldKey={this.layoutDoc.sidebarViewType === 'translation' ? `${this.fieldKey}_translation` : `${this.fieldKey}_sidebar`}
+                        fieldKey={this.layoutDoc.sidebar_collectionType === 'translation' ? `${this.fieldKey}_translation` : `${this.fieldKey}_sidebar`}
                     />
                 </div>
             );
         };
         return (
             <div className={'formattedTextBox-sidebar' + (Doc.ActiveTool !== InkTool.None ? '-inking' : '')} style={{ width: `${this.layout_sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
-                {renderComponent(StrCast(this.layoutDoc.sidebarViewType))}
+                {renderComponent(StrCast(this.layoutDoc.sidebar_collectionType))}
             </div>
         );
     }
     cycleAlternateText = () => {
-        if (this.layoutDoc._layout_altContentUI) {
+        if (this.layoutDoc._layout_enableAltContentUI) {
             const usePath = this.rootDoc[`${this.props.fieldKey}_usePath`];
             this.rootDoc[`_${this.props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined;
         }
@@ -2016,7 +2016,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         const active = this.props.isContentActive() || this.props.isSelected();
         const selected = active;
         const scale = (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1);
-        const rounded = StrCast(this.layoutDoc.borderRounding) === '100%' ? '-rounded' : '';
+        const rounded = StrCast(this.layoutDoc.layout_borderRounding) === '100%' ? '-rounded' : '';
         const interactive = (Doc.ActiveTool === InkTool.None || SnappingManager.GetIsDragging()) && (this.layoutDoc.z || !this.layoutDoc._lockedPosition);
         if (!selected && FormattedTextBoxComment.textBox === this) setTimeout(FormattedTextBoxComment.Hide);
         const minimal = this.props.ignoreAutoHeight;
@@ -2096,7 +2096,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     {this.noSidebar || this.props.dontSelectOnLoad || !this.SidebarShown || this.layout_sidebarWidthPercent === '0%' ? null : this.sidebarCollection}
                     {this.noSidebar || this.Document._layout_noSidebar || this.props.dontSelectOnLoad || this.Document._createDocOnCR ? null : this.sidebarHandle}
                     {this.audioHandle}
-                    {this.layoutDoc._layout_altContentUI ? this.overlayAlternateIcon : null}
+                    {this.layoutDoc._layout_enableAltContentUI ? this.overlayAlternateIcon : null}
                 </div>
             </div>
         );
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 884811317..ac20126f1 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -488,8 +488,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         }
 
         if ((pinDataTypes?.filters && activeItem.presDocFilters !== undefined) || (!pinDataTypes && activeItem.presDocFilters !== undefined)) {
-            if (bestTarget.docFilters !== activeItem.presDocFilters) {
-                bestTarget.docFilters = ObjectField.MakeCopy(activeItem.presDocFilters as ObjectField) || new List<string>([]);
+            if (bestTarget.childFilters !== activeItem.presDocFilters) {
+                bestTarget.childFilters = ObjectField.MakeCopy(activeItem.presDocFilters as ObjectField) || new List<string>([]);
                 changed = true;
             }
         }
@@ -651,7 +651,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     )
                 );
             if (pinProps.pinData.type_collection) pinDoc.presViewType = targetDoc._type_collection;
-            if (pinProps.pinData.filters) pinDoc.presDocFilters = ObjectField.MakeCopy(targetDoc.docFilters as ObjectField);
+            if (pinProps.pinData.filters) pinDoc.presDocFilters = ObjectField.MakeCopy(targetDoc.childFilters as ObjectField);
             if (pinProps.pinData.pivot) pinDoc.presPivotField = targetDoc._pivotField;
             if (pinProps.pinData.pannable) {
                 pinDoc.presPanX = NumCast(targetDoc._freeform_panX);
@@ -2078,10 +2078,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         // prettier-ignore
         switch (layout) {
             case 'blank': return Docs.Create.FreeformDocument([], { title: input ? input : 'Blank slide', _width: 400, _height: 225, x, y });
-            case 'title': return Docs.Create.FreeformDocument([title(), subtitle()], { title: input ? input : 'Title slide', _width: 400, _height: 225, _layoutFitContentsToBox: true, x, y });
-            case 'header': return Docs.Create.FreeformDocument([header()], { title: input ? input : 'Section header', _width: 400, _height: 225, _layoutFitContentsToBox: true, x, y });
-            case 'content': return Docs.Create.FreeformDocument([contentTitle(), content()], { title: input ? input : 'Title and content', _width: 400, _height: 225, _layoutFitContentsToBox: true, x, y });
-            case 'twoColumns': return Docs.Create.FreeformDocument([contentTitle(), content1(), content2()], { title: input ? input : 'Title and two columns', _width: 400, _height: 225, _layoutFitContentsToBox: true, x, y })
+            case 'title': return Docs.Create.FreeformDocument([title(), subtitle()], { title: input ? input : 'Title slide', _width: 400, _height: 225, _layout_fitContentsToBox: true, x, y });
+            case 'header': return Docs.Create.FreeformDocument([header()], { title: input ? input : 'Section header', _width: 400, _height: 225, _layout_fitContentsToBox: true, x, y });
+            case 'content': return Docs.Create.FreeformDocument([contentTitle(), content()], { title: input ? input : 'Title and content', _width: 400, _height: 225, _layout_fitContentsToBox: true, x, y });
+            case 'twoColumns': return Docs.Create.FreeformDocument([contentTitle(), content1(), content2()], { title: input ? input : 'Title and two columns', _width: 400, _height: 225, _layout_fitContentsToBox: true, x, y })
         }
     };
 
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 154c879a0..34e069046 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -107,8 +107,8 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     ScreenToLocalTransform={Transform.Identity}
                     renderDepth={this.props.renderDepth + 1}
                     docViewPath={returnEmptyDoclist}
-                    docFilters={this.props.docFilters}
-                    docRangeFilters={this.props.docRangeFilters}
+                    childFilters={this.props.childFilters}
+                    childFiltersByRanges={this.props.childFiltersByRanges}
                     searchFilterDocs={this.props.searchFilterDocs}
                     rootSelected={returnTrue}
                     addDocument={returnFalse}
@@ -377,9 +377,9 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
             const recording = Docs.Create.WebCamDocument('', {
                 _width: 384,
                 _height: 216,
-                hideDocumentButtonBar: true,
+                layout_hideDocumentButtonBar: true,
                 layout_hideDecorationTitle: true,
-                hideOpenButton: true,
+                layout_hideOpenButton: true,
                 // hideDeleteButton: true,
                 cloneFieldFilter: new List<string>(['isSystem']),
             });
@@ -522,7 +522,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         style={{
                             display: 'infline-block',
                             backgroundColor: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor),
-                            //boxShadow: presBoxColor && presBoxColor !== 'white' && presBoxColor !== 'transparent' ? (isCurrent ? '0 0 0px 1.5px' + presBoxColor : undefined) : undefined,
+                            //layout_boxShadow: presBoxColor && presBoxColor !== 'white' && presBoxColor !== 'transparent' ? (isCurrent ? '0 0 0px 1.5px' + presBoxColor : undefined) : undefined,
                             border: presBoxColor && presBoxColor !== 'white' && presBoxColor !== 'transparent' ? (isCurrent ? presBoxColor + ' solid 2.5px' : undefined) : undefined,
                         }}>
                         <div
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index bc0cad636..dd202418b 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -91,7 +91,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
     @observable isAnnotating = false;
     // key where data is stored
     @computed get allAnnotations() {
-        return DocUtils.FilterDocs(DocListCast(this.props.dataDoc[this.props.fieldKey + '_annotations']), this.props.docFilters(), this.props.docRangeFilters());
+        return DocUtils.FilterDocs(DocListCast(this.props.dataDoc[this.props.fieldKey + '_annotations']), this.props.childFilters(), this.props.childFiltersByRanges());
     }
     @computed get inlineTextAnnotations() {
         return this.allAnnotations.filter(a => a.textInlineAnnotations);
@@ -513,8 +513,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
     overlayTransform = () => this.scrollXf().scale(1 / NumCast(this.props.layoutDoc._freeform_scale, 1));
     panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1);
     panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
-    transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()];
-    opaqueFilter = () => [...this.props.docFilters(), Utils.noDragsDocFilter, ...(DragManager.docsBeingDragged.length ? [] : [Utils.IsOpaqueFilter()])];
+    transparentFilter = () => [...this.props.childFilters(), Utils.IsTransparentFilter()];
+    opaqueFilter = () => [...this.props.childFilters(), Utils.noDragsDocFilter, ...(DragManager.docsBeingDragged.length ? [] : [Utils.IsOpaqueFilter()])];
     childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
         if (doc instanceof Doc && property === StyleProp.PointerEvents) {
             if (this.inlineTextAnnotations.includes(doc)) return 'none';
@@ -523,7 +523,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
         return this.props.styleProvider?.(doc, props, property);
     };
 
-    renderAnnotations = (docFilters: () => string[], mixBlendMode?: any, display?: string) => (
+    renderAnnotations = (childFilters: () => string[], mixBlendMode?: any, display?: string) => (
         <div
             className="pdfViewerDash-overlay"
             style={{
@@ -550,7 +550,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
                 isAnyChildContentActive={returnFalse}
                 isAnnotationOverlayScrollable={true}
                 dropAction="embed"
-                docFilters={docFilters}
+                childFilters={childFilters}
                 select={emptyFunction}
                 bringToFront={emptyFunction}
                 styleProvider={this.childStyleProvider}
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 72eeedcff..92ae64567 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1415,18 +1415,18 @@ export namespace Doc {
     }
     export function setDocRangeFilter(container: Opt<Doc>, key: string, range?: number[]) {
         if (!container) return;
-        const docRangeFilters = Cast(container._docRangeFilters, listSpec('string'), []);
-        for (let i = 0; i < docRangeFilters.length; i += 3) {
-            if (docRangeFilters[i] === key) {
-                docRangeFilters.splice(i, 3);
+        const childFiltersByRanges = Cast(container._childFiltersByRanges, listSpec('string'), []);
+        for (let i = 0; i < childFiltersByRanges.length; i += 3) {
+            if (childFiltersByRanges[i] === key) {
+                childFiltersByRanges.splice(i, 3);
                 break;
             }
         }
         if (range !== undefined) {
-            docRangeFilters.push(key);
-            docRangeFilters.push(range[0].toString());
-            docRangeFilters.push(range[1].toString());
-            container._docRangeFilters = new List<string>(docRangeFilters);
+            childFiltersByRanges.push(key);
+            childFiltersByRanges.push(range[0].toString());
+            childFiltersByRanges.push(range[1].toString());
+            container._childFiltersByRanges = new List<string>(childFiltersByRanges);
         }
     }
 
@@ -1435,35 +1435,35 @@ export namespace Doc {
     // based on the modifiers :"check", "x", undefined
     export function setDocFilter(container: Opt<Doc>, key: string, value: any, modifiers: 'remove' | 'match' | 'check' | 'x' | 'exists' | 'unset', toggle?: boolean, fieldPrefix?: string, append: boolean = true) {
         if (!container) return;
-        const filterField = '_' + (fieldPrefix ? fieldPrefix + '_' : '') + 'docFilters';
-        const docFilters = StrListCast(container[filterField]);
+        const filterField = '_' + (fieldPrefix ? fieldPrefix + '_' : '') + 'childFilters';
+        const childFilters = StrListCast(container[filterField]);
         runInAction(() => {
-            for (let i = 0; i < docFilters.length; i++) {
-                const fields = docFilters[i].split(':'); // split key:value:modifier
+            for (let i = 0; i < childFilters.length; i++) {
+                const fields = childFilters[i].split(':'); // split key:value:modifier
                 if (fields[0] === key && (fields[1] === value || modifiers === 'match' || (fields[2] === 'match' && modifiers === 'remove'))) {
                     if (fields[2] === modifiers && modifiers && fields[1] === value) {
                         if (toggle) modifiers = 'remove';
                         else return;
                     }
-                    docFilters.splice(i, 1);
-                    container[filterField] = new List<string>(docFilters);
+                    childFilters.splice(i, 1);
+                    container[filterField] = new List<string>(childFilters);
                     break;
                 }
             }
-            if (!docFilters.length && modifiers === 'match' && value === undefined) {
+            if (!childFilters.length && modifiers === 'match' && value === undefined) {
                 container[filterField] = undefined;
             } else if (modifiers !== 'remove') {
-                !append && (docFilters.length = 0);
-                docFilters.push(key + ':' + value + ':' + modifiers);
-                container[filterField] = new List<string>(docFilters);
+                !append && (childFilters.length = 0);
+                childFilters.push(key + ':' + value + ':' + modifiers);
+                container[filterField] = new List<string>(childFilters);
             }
         });
     }
     export function readDocRangeFilter(doc: Doc, key: string) {
-        const docRangeFilters = Cast(doc._docRangeFilters, listSpec('string'), []);
-        for (let i = 0; i < docRangeFilters.length; i += 3) {
-            if (docRangeFilters[i] === key) {
-                return [Number(docRangeFilters[i + 1]), Number(docRangeFilters[i + 2])];
+        const childFiltersByRanges = Cast(doc._childFiltersByRanges, listSpec('string'), []);
+        for (let i = 0; i < childFiltersByRanges.length; i += 3) {
+            if (childFiltersByRanges[i] === key) {
+                return [Number(childFiltersByRanges[i + 1]), Number(childFiltersByRanges[i + 2])];
             }
         }
     }
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index 7a0a18929..757d507be 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -55,8 +55,8 @@ export const documentSchema = createSchema({
 
     // appearance properties on the data document
     backgroundColor: 'string', // background color of document
-    borderRounding: 'string', // border radius rounding of document
-    boxShadow: 'string', // the amount of shadow around the perimeter of a document
+    layout_borderRounding: 'string', // border radius rounding of document
+    layout_boxShadow: 'string', // the amount of shadow around the perimeter of a document
     color: 'string', // foreground color of document
     freeform_fitContentsToBox: 'boolean', // whether freeform view contents should be zoomed/panned to fill the area of the document view box
     fontSize: 'string',
@@ -86,13 +86,13 @@ export const documentSchema = createSchema({
     onDragStart: ScriptField, // script to run when document is dragged (without being selected).  the script should return the Doc to be dropped.
     followLinkLocation: 'string', // flag for where to place content when following a click interaction (e.g., add:right, lightbox, default, )
     hideLinkButton: 'boolean', // whether the blue link counter button should be hidden
-    hideAllLinks: 'boolean', // whether all individual blue anchor dots should be hidden
-    layout_linkDisplay: 'boolean', // whether a link connection should be shown between link anchor endpoints.
+    layout_hideAllLinks: 'boolean', // whether all individual blue anchor dots should be hidden
+    link_displayLine: 'boolean', // whether a link connection should be shown between link anchor endpoints.
     isLightbox: 'boolean', // whether the marked object will display addDocTab() calls that target "lightbox" destinations
     layers: listSpec('string'), // which layers the document is part of
     _lockedPosition: 'boolean', // whether the document can be moved (dragged)
     _lockedTransform: 'boolean', // whether a freeformview can pan/zoom
-    layout_linkDisplayArrow: 'boolean', // toggles directed arrows
+    link_displayArrow: 'boolean', // toggles directed arrows
 
     // drag drop properties
     _stayInCollection: 'boolean', // whether document can be dropped into a different collection
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 62f27c7d2..f365adf4b 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -16,7 +16,7 @@ import { PrefetchProxy, ProxyField } from './Proxy';
 import { RefField } from './RefField';
 import { RichTextField } from './RichTextField';
 import { SchemaHeaderField } from './SchemaHeaderField';
-import { ComputedField } from './ScriptField';
+import { ComputedField, ScriptField } from './ScriptField';
 import { ScriptCast, StrCast } from './Types';
 
 function _readOnlySetter(): never {
@@ -69,10 +69,12 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
 
     if (writeToDoc) {
         if (value === undefined) {
-            delete target[FieldKeys][prop];
+            target[FieldKeys] && delete target[FieldKeys][prop]; // Lists don't have a FieldKeys field
             delete target.__fieldTuples[prop];
         } else {
-            target[FieldKeys][prop] = true;
+            // bcz: uncomment to see if server is being updated
+            // console.log(prop + ' = ' + value + '(' + curValue + ')');
+            target[FieldKeys] && (target[FieldKeys][prop] = true); // Lists don't have a FieldKeys field
             target.__fieldTuples[prop] = value;
         }
 
diff --git a/src/mobile/AudioUpload.tsx b/src/mobile/AudioUpload.tsx
index c3423e2a5..7a1dde9fb 100644
--- a/src/mobile/AudioUpload.tsx
+++ b/src/mobile/AudioUpload.tsx
@@ -25,8 +25,8 @@ export class AudioUpload extends React.Component {
                 title: 'mobile audio',
                 _width: 300,
                 _height: 300,
-                _layoutFitContentsToBox: true,
-                boxShadow: '0 0',
+                _layout_fitContentsToBox: true,
+                layout_boxShadow: '0 0',
             }),
             Doc
         )
@@ -54,7 +54,7 @@ export class AudioUpload extends React.Component {
                             Doc
                         ) as Doc,
                     ],
-                    { title: 'mobile audio', _width: 300, _height: 300, _layoutFitContentsToBox: true, boxShadow: '0 0' }
+                    { title: 'mobile audio', _width: 300, _height: 300, _layout_fitContentsToBox: true, layout_boxShadow: '0 0' }
                 ),
                 Doc
             )
@@ -110,8 +110,8 @@ export class AudioUpload extends React.Component {
                         pinToPres={emptyFunction}
                         rootSelected={returnTrue}
                         removeDocument={undefined}
-                        docFilters={returnEmptyFilter}
-                        docRangeFilters={returnEmptyFilter}
+                        childFilters={returnEmptyFilter}
+                        childFiltersByRanges={returnEmptyFilter}
                         searchFilterDocs={returnEmptyDoclist}
                         ScreenToLocalTransform={Transform.Identity}
                         PanelWidth={() => 600}
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 471dc568b..584a7b432 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -402,8 +402,8 @@ export class MobileInterface extends React.Component {
                     docViewPath={returnEmptyDoclist}
                     whenChildContentsActiveChanged={emptyFunction}
                     bringToFront={emptyFunction}
-                    docFilters={returnEmptyFilter}
-                    docRangeFilters={returnEmptyFilter}
+                    childFilters={returnEmptyFilter}
+                    childFiltersByRanges={returnEmptyFilter}
                     searchFilterDocs={returnEmptyDoclist}
                 />
             </div>
-- 
cgit v1.2.3-70-g09d2


From c080f6aa2abd0539e61486032c3483a1108b9e14 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 19 Jun 2023 19:00:48 -0400
Subject: renamed all config doc creators to just ConfigDocument()

---
 src/client/documents/Documents.ts                  | 29 +---------------------
 src/client/views/InkingStroke.tsx                  |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 src/client/views/nodes/ComparisonBox.tsx           |  2 +-
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   |  2 +-
 .../nodes/DataVizBox/components/LineChart.tsx      |  2 +-
 src/client/views/nodes/FunctionPlotBox.tsx         |  2 +-
 src/client/views/nodes/ImageBox.tsx                |  2 +-
 src/client/views/nodes/PDFBox.tsx                  |  6 ++---
 src/client/views/nodes/WebBox.tsx                  |  2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  6 ++---
 src/client/views/nodes/trails/PresBox.tsx          |  8 ++++--
 12 files changed, 20 insertions(+), 45 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index a91d5806c..ffde9fe1b 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1054,34 +1054,7 @@ export namespace Docs {
             return inst;
         }
 
-        export function WebConfigDocument(options: DocumentOptions = {}, id?: string) {
-            return InstanceFromProto(Prototypes.get(DocumentType.CONFIG), undefined, options, id);
-        }
-        export function CollectionConfigDocument(options: DocumentOptions = {}, id?: string) {
-            return InstanceFromProto(Prototypes.get(DocumentType.CONFIG), options?.data, options, id);
-        }
-        export function PdfConfigDocument(options: DocumentOptions = {}, id?: string) {
-            return InstanceFromProto(Prototypes.get(DocumentType.CONFIG), options?.data, options, id);
-        }
-        export function TextConfigDocument(options: DocumentOptions = {}, id?: string) {
-            return InstanceFromProto(Prototypes.get(DocumentType.CONFIG), options?.data, options, id);
-        }
-        export function FunctionPlotConfigDocument(options: DocumentOptions = {}, id?: string) {
-            return InstanceFromProto(Prototypes.get(DocumentType.CONFIG), options?.data, options, id);
-        }
-        export function DataVizConfigDocument(options: DocumentOptions = {}, id?: string) {
-            return InstanceFromProto(Prototypes.get(DocumentType.CONFIG), options?.data, options, id);
-        }
-        export function LineChartConfigDocument(options: DocumentOptions = {}, id?: string) {
-            return InstanceFromProto(Prototypes.get(DocumentType.CONFIG), options?.data, options, id);
-        }
-        export function ComparisonConfigDocument(options: DocumentOptions = {}, id?: string) {
-            return InstanceFromProto(Prototypes.get(DocumentType.CONFIG), options?.data, options, id);
-        }
-        export function ImageConfigDocument(options: DocumentOptions = {}, id?: string) {
-            return InstanceFromProto(Prototypes.get(DocumentType.CONFIG), options?.data, options, id);
-        }
-        export function InkConfigDocument(options: DocumentOptions, id?: string) {
+        export function ConfigDocument(options: DocumentOptions, id?: string) {
             return InstanceFromProto(Prototypes.get(DocumentType.CONFIG), options?.data, options, id);
         }
 
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 352237bdb..d0210d63b 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -88,7 +88,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
 
         if (!addAsAnnotation && !pinProps) return this.rootDoc;
 
-        const anchor = Docs.Create.InkConfigDocument({
+        const anchor = Docs.Create.ConfigDocument({
             title: 'Ink anchor:' + this.rootDoc.title,
             // set presentation timing for restoring shape
             presDuration: 1100,
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 11151e74e..36472dd2e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1535,7 +1535,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
         // create an anchor that saves information about the current state of the freeform view (pan, zoom, view type)
-        const anchor = Docs.Create.CollectionConfigDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._type_collection), layout_unrendered: true, presTransition: 500, annotationOn: this.rootDoc });
+        const anchor = Docs.Create.ConfigDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._type_collection), layout_unrendered: true, presTransition: 500, annotationOn: this.rootDoc });
         PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: !this.Document._isGroup, type_collection: true, filters: true } }, this.rootDoc);
 
         if (addAsAnnotation) {
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 1cc09a63c..e6a4635c0 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -85,7 +85,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
     };
 
     getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
-        const anchor = Docs.Create.ComparisonConfigDocument({
+        const anchor = Docs.Create.ConfigDocument({
             title: 'ImgAnchor:' + this.rootDoc.title,
             // set presentation timing properties for restoring view
             presTransition: 1000,
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index baa45e278..0fe24fe8d 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -73,7 +73,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     getAnchor = (addAsAnnotation?: boolean, pinProps?: PinProps) => {
         const anchor =
             this._chartRenderer?.getAnchor(pinProps) ??
-            Docs.Create.DataVizConfigDocument({
+            Docs.Create.ConfigDocument({
                 // when we clear selection -> we should have it so chartBox getAnchor returns undefined
                 // this is for when we want the whole doc (so when the chartBox getAnchor returns without a marker)
                 /*put in some options*/
diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
index 661061d51..6b564b0c9 100644
--- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
@@ -163,7 +163,7 @@ export class LineChart extends React.Component<LineChartProps> {
 
     // create a document anchor that stores whatever is needed to reconstruct the viewing state (selection,zoom,etc)
     getAnchor = (pinProps?: PinProps) => {
-        const anchor = Docs.Create.LineChartConfigDocument({
+        const anchor = Docs.Create.ConfigDocument({
             //
             title: 'line doc selection' + this._currSelected?.x,
         });
diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx
index 1a78583f9..8a88bede6 100644
--- a/src/client/views/nodes/FunctionPlotBox.tsx
+++ b/src/client/views/nodes/FunctionPlotBox.tsx
@@ -43,7 +43,7 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
         );
     }
     getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
-        const anchor = Docs.Create.FunctionPlotConfigDocument({
+        const anchor = Docs.Create.ConfigDocument({
             //
             annotationOn: this.rootDoc,
         });
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 5b302e7ce..9acbee1e7 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -78,7 +78,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
         const anchor =
             this._getAnchor?.(this._savedAnnotations, false) ?? // use marquee anchor, otherwise, save zoom/pan as anchor
-            Docs.Create.ImageConfigDocument({
+            Docs.Create.ConfigDocument({
                 title: 'ImgAnchor:' + this.rootDoc.title,
                 presPanX: NumCast(this.layoutDoc._freeform_panX),
                 presPanY: NumCast(this.layoutDoc._freeform_panY),
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index c210176b0..6d040ca1a 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -238,13 +238,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             ele = document.createElement('div');
             ele.append(this._pdfViewer.selectionContent()!);
         }
-        const docAnchor = () => {
-            const anchor = Docs.Create.PdfConfigDocument({
+        const docAnchor = () =>
+            Docs.Create.ConfigDocument({
                 title: StrCast(this.rootDoc.title + '@' + NumCast(this.layoutDoc._layout_scrollTop)?.toFixed(0)),
                 annotationOn: this.rootDoc,
             });
-            return anchor;
-        };
         const annoAnchor = this._pdfViewer?._getAnchor(this._pdfViewer.savedAnnotations(), true);
         const anchor = annoAnchor ?? docAnchor();
         PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), scrollable: true, pannable: true } }, this.rootDoc);
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 2ff0245d2..16705a6b3 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -323,7 +323,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         } catch (e) {}
         const anchor =
             this._getAnchor(this._savedAnnotations, false) ??
-            Docs.Create.WebConfigDocument({
+            Docs.Create.ConfigDocument({
                 title: StrCast(this.rootDoc.title + ' ' + this.layoutDoc._layout_scrollTop),
                 y: NumCast(this.layoutDoc._layout_scrollTop),
                 annotationOn: this.rootDoc,
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 0dc186eaf..079129dc9 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -244,7 +244,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
 
     getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
         if (!pinProps && this._editorView?.state.selection.empty) return this.rootDoc;
-        const anchor = Docs.Create.TextConfigDocument({ title: StrCast(this.rootDoc.title), annotationOn: this.rootDoc });
+        const anchor = Docs.Create.ConfigDocument({ title: StrCast(this.rootDoc.title), annotationOn: this.rootDoc });
         this.addDocument(anchor);
         this.makeLinkAnchor(anchor, OpenWhere.addRight, undefined, 'Anchored Selection', false, addAsAnnotation);
         PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), scrollable: true } }, this.rootDoc);
@@ -963,7 +963,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         if (this._editorView && this._recordingStart) {
             if (this._break) {
                 const textanchorFunc = () => {
-                    const tanch = Docs.Create.TextConfigDocument({ title: 'dictation anchor' });
+                    const tanch = Docs.Create.ConfigDocument({ title: 'dictation anchor' });
                     return this.addDocument(tanch) ? tanch : undefined;
                 };
                 const link = DocUtils.MakeLinkToActiveAudio(textanchorFunc, false).lastElement();
@@ -1003,7 +1003,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             if (sel.from !== sel.to) {
                 const anchor =
                     anchorDoc ??
-                    Docs.Create.TextConfigDocument({
+                    Docs.Create.ConfigDocument({
                         //
                         title: 'text(' + this._editorView?.state.doc.textBetween(sel.from, sel.to) + ')',
                         annotationOn: this.dataDoc,
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 505e39d97..1e622e9ff 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -300,8 +300,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         listItemDoc.presTransition = 500;
                         targetView?.setAnimEffect(listItemDoc, 500);
                         if (targetView?.docView && this.activeItem.presBulletExpand) {
-                            targetView.docView._animateScalingTo = 1.1;
-                            Doc.AddUnHighlightWatcher(() => (targetView!.docView!._animateScalingTo = 0));
+                            targetView.docView._animateScalingTo = 1.2;
+                            targetView.docView._animateScaleTime = 400;
+                            Doc.AddUnHighlightWatcher(() => {
+                                targetView.docView._animateScaleTime = undefined;
+                                targetView!.docView!._animateScalingTo = 0;
+                            });
                         }
                         listItemDoc.opacity = undefined;
                         this.activeItem.presIndexed = presIndexed + 1;
-- 
cgit v1.2.3-70-g09d2


From 72dad52c4fea5a2fccabacbb6a49ca1494093954 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 22 Jun 2023 10:14:26 -0400
Subject: cleaned up dragging - abort presItem drags properly, fix naming for
 drag/dropAction, small fix undo of drag removeproperties, dragging in/out of
 headerBar,  fixed isContentActive to avoid SnappingManager.GetIsDragging. 
 fixed resizing videos.

---
 src/client/documents/Documents.ts                  | 49 +++++------
 src/client/util/CurrentUserUtils.ts                | 81 +++++++++---------
 src/client/util/DragManager.ts                     | 38 ++++-----
 src/client/util/DropConverter.ts                   |  8 +-
 src/client/util/UndoManager.ts                     | 19 ++---
 src/client/views/DashboardView.tsx                 |  6 +-
 src/client/views/DocComponent.tsx                  |  6 +-
 src/client/views/DocumentDecorations.tsx           |  8 +-
 src/client/views/GestureOverlay.tsx                | 95 +---------------------
 src/client/views/MainView.tsx                      | 12 ++-
 src/client/views/Palette.scss                      | 30 -------
 src/client/views/Palette.tsx                       | 69 ----------------
 src/client/views/PropertiesView.tsx                |  1 -
 src/client/views/StyleProvider.tsx                 |  1 +
 src/client/views/TemplateMenu.tsx                  |  5 +-
 .../views/collections/CollectionCarouselView.tsx   |  4 +-
 .../views/collections/CollectionNoteTakingView.tsx |  6 +-
 .../views/collections/CollectionPileView.tsx       | 11 ++-
 .../collections/CollectionStackedTimeline.scss     |  2 +-
 .../collections/CollectionStackedTimeline.tsx      | 10 ++-
 .../views/collections/CollectionStackingView.tsx   | 18 ++--
 src/client/views/collections/CollectionSubView.tsx | 10 +--
 .../views/collections/CollectionTimeView.tsx       |  2 -
 .../views/collections/CollectionTreeView.tsx       | 14 ++--
 src/client/views/collections/CollectionView.tsx    |  5 +-
 src/client/views/collections/TabDocView.tsx        |  1 -
 src/client/views/collections/TreeView.tsx          | 29 ++++---
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 11 ++-
 .../collections/collectionFreeForm/MarqueeView.tsx |  1 -
 .../collectionGrid/CollectionGridView.tsx          |  2 +-
 .../collectionLinear/CollectionLinearView.tsx      | 40 ++-------
 .../CollectionMulticolumnView.tsx                  | 18 ++--
 .../CollectionMultirowView.tsx                     | 19 +++--
 .../collectionSchema/CollectionSchemaView.tsx      |  3 +-
 .../collections/collectionSchema/SchemaRowBox.tsx  |  8 +-
 .../collectionSchema/SchemaTableCell.tsx           |  2 +-
 src/client/views/linking/LinkMenuItem.tsx          |  2 +-
 src/client/views/linking/LinkPopup.tsx             |  1 -
 src/client/views/nodes/ComparisonBox.scss          |  2 +-
 src/client/views/nodes/ComparisonBox.tsx           | 80 ++++++++++++------
 src/client/views/nodes/DocumentView.tsx            | 52 +++++++-----
 src/client/views/nodes/FieldView.tsx               |  2 +-
 src/client/views/nodes/ImageBox.tsx                |  4 +-
 src/client/views/nodes/KeyValueBox.tsx             |  8 +-
 src/client/views/nodes/KeyValuePair.tsx            |  1 -
 src/client/views/nodes/LinkAnchorBox.tsx           |  2 +-
 src/client/views/nodes/MapBox/MapBox.tsx           |  5 +-
 src/client/views/nodes/MapBox/MapBox2.tsx          |  2 +-
 src/client/views/nodes/PDFBox.tsx                  |  2 -
 src/client/views/nodes/WebBox.tsx                  | 14 +++-
 .../views/nodes/formattedText/FormattedTextBox.tsx | 21 ++---
 src/client/views/nodes/trails/PresBox.tsx          |  4 +-
 src/client/views/nodes/trails/PresElementBox.tsx   | 13 +--
 src/client/views/pdf/PDFViewer.tsx                 | 18 ++--
 src/fields/Doc.ts                                  |  2 +-
 src/fields/documentSchemas.ts                      | 10 +--
 src/fields/util.ts                                 |  4 +-
 57 files changed, 356 insertions(+), 537 deletions(-)
 delete mode 100644 src/client/views/Palette.scss
 delete mode 100644 src/client/views/Palette.tsx

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index ffde9fe1b..a61ef1da3 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -25,7 +25,7 @@ import { DirectoryImportBox } from '../util/Import & Export/DirectoryImportBox';
 import { FollowLinkScript } from '../util/LinkFollower';
 import { LinkManager } from '../util/LinkManager';
 import { ScriptingGlobals } from '../util/ScriptingGlobals';
-import { undoable, undoBatch, UndoManager } from '../util/UndoManager';
+import { undoable, UndoManager } from '../util/UndoManager';
 import { CollectionDockingView } from '../views/collections/CollectionDockingView';
 import { DimUnit } from '../views/collections/collectionMulticolumn/CollectionMulticolumnView';
 import { CollectionView } from '../views/collections/CollectionView';
@@ -184,7 +184,8 @@ export class DocumentOptions {
     opacity?: NUMt = new NumInfo('document opacity');
     viewTransitionTime?: NUMt = new NumInfo('transition duration for view parameters');
     dontRegisterView?: BOOLt = new BoolInfo('are views of this document registered so that they can be found when following links, etc');
-    dontUndo?: BOOLt = new BoolInfo('whether button clicks should be undoable (true for Undo/Redo/and sidebar) AND whether modifications to document are undoable (true for linearview menu buttons to prevent open/close from entering undo stack)');
+    _undoIgnoreFields?: List<string>; //'fields that should not be added to the undo stack (opacity for Undo/Redo/and sidebar) AND whether modifications to document are undoable (true for linearview menu buttons to prevent open/close from entering undo stack)'
+    undoIgnoreFields?: List<string>; //'fields that should not be added to the undo stack (opacity for Undo/Redo/and sidebar) AND whether modifications to document are undoable (true for linearview menu buttons to prevent open/close from entering undo stack)'
     _headerHeight?: NUMt = new NumInfo('height of document header used for displaying title');
     _headerFontSize?: NUMt = new NumInfo('font size of header of custom notes');
     _headerPointerEvents?: PEVt = new PEInfo('types of events the header of a custom text document can consume');
@@ -355,20 +356,19 @@ export class DocumentOptions {
     waitForDoubleClickToClick?: 'always' | 'never' | 'default'; // whether a click function wait for double click to expire.  'default' undefined = wait only if there's a click handler, "never" = never wait, "always" = alway wait
     onPointerDown?: ScriptField;
     onPointerUp?: ScriptField;
-    dragFactory_count?: NUMt = new NumInfo('number of items created from a drag button (used for setting title with incrementing index)', true);
     openFactoryLocation?: string; // an OpenWhere value to place the factory created document
     openFactoryAsDelegate?: BOOLt = new BoolInfo('create a delegate of the factory');
     _forceActive?: BOOLt = new BoolInfo('flag to handle pointer events when not selected (or otherwise active)');
-    enableDragWhenActive?: BOOLt = new BoolInfo('allow dragging even if document contentts are active (e.g., tree, groups)');
-    _stayInCollection?: BOOLt = new BoolInfo('whether the document should remain in its collection when someone tries to drag and drop it elsewhere');
+    _dragOnlyWithinContainer?: BOOLt = new BoolInfo('whether the document should remain in its collection when someone tries to drag and drop it elsewhere');
     _raiseWhenDragged?: BOOLt = new BoolInfo('whether a document is brought to front when dragged.');
-    allowOverlayDrop?: BOOLt = new BoolInfo('can documents be dropped onto this document without using dragging title bar or holding down embed key (ctrl)?', true);
-    childDropAction?: DROPt = new DAInfo("what should happen to the source document when it's dropped onto a child of a collection ");
-    targetDropAction?: DROPt = new DAInfo('what should happen to the source document when ??? ');
+    childDragAction?: DROPt = new DAInfo('what should happen to the child documents when they are dragged fromt he collection');
+    dropConverter?: ScriptField; // script to run when documents are dropped on this Document.
+    dropAction?: DROPt = new DAInfo("what should happen to this document when it's dropped somewhere else");
     _dropAction?: DROPt = new DAInfo("what should happen to this document when it's dropped somewhere else");
-    _removeDropProperties?: List<string>; // list of properties that should be removed from a document when it is dropped.  e.g., a creator button may be forceActive to allow it be dragged, but the forceActive property can be removed from the dropped document
+    _dropPropertiesToRemove?: List<string>; // list of properties that should be removed from a document when it is dropped.  e.g., a creator button may be forceActive to allow it be dragged, but the forceActive property can be removed from the dropped document
     cloneFieldFilter?: List<string>; // fields not to copy when the document is clonedclipboard?: Doc;
-    dropConverter?: ScriptField; // script to run when documents are dropped on this Document.
+    dragAction?: DROPt = new DAInfo('how to drag document when it is active (e.g., tree, groups)');
+    dragFactory_count?: NUMt = new NumInfo('number of items created from a drag button (used for setting title with incrementing index)', true);
     dragFactory?: Doc; // document to create when dragging with a suitable onDragStart script
     clickFactory?: Doc; // document to create when clicking on a button with a suitable onClick script
     onDragStart?: ScriptField; //script to execute at start of drag operation --  e.g., when a "creator" button is dragged this script generates a different document to drop
@@ -536,7 +536,7 @@ export namespace Docs {
                         link_description: '',
                         layout_showCaption: 'link_description',
                         backgroundColor: 'lightblue', // lightblue is default color for linking dot and link documents text comment area
-                        _removeDropProperties: new List(['onClick']),
+                        _dropPropertiesToRemove: new List(['onClick']),
                     },
                 },
             ],
@@ -545,7 +545,7 @@ export namespace Docs {
                 {
                     data: new List<Doc>(),
                     layout: { view: EmptyBox, dataField: defaultDataKey },
-                    options: { childDropAction: 'embed', title: 'Global Link Database' },
+                    options: { title: 'Global Link Database' },
                 },
             ],
             [
@@ -553,7 +553,7 @@ export namespace Docs {
                 {
                     data: new List<Doc>(),
                     layout: { view: EmptyBox, dataField: defaultDataKey },
-                    options: { childDropAction: 'embed', title: 'Global Script Database' },
+                    options: { title: 'Global Script Database' },
                 },
             ],
             [
@@ -615,7 +615,7 @@ export namespace Docs {
                 DocumentType.FONTICON,
                 {
                     layout: { view: FontIconBox, dataField: 'icon' },
-                    options: { defaultDoubleClick: 'ignore', waitForDoubleClickToClick: 'never', enableDragWhenActive: true, layout_hideLinkButton: true, _width: 40, _height: 40 },
+                    options: { defaultDoubleClick: 'ignore', waitForDoubleClickToClick: 'never', layout_hideLinkButton: true, _width: 40, _height: 40 },
                 },
             ],
             [
@@ -659,7 +659,7 @@ export namespace Docs {
                 {
                     data: '',
                     layout: { view: ComparisonBox, dataField: defaultDataKey },
-                    options: { backgroundColor: 'gray', targetDropAction: 'embed' },
+                    options: { backgroundColor: 'gray', dropAction: 'move', waitForDoubleClickToClick: 'always' },
                 },
             ],
             [
@@ -667,7 +667,7 @@ export namespace Docs {
                 {
                     data: new List<Doc>(),
                     layout: { view: EmptyBox, dataField: defaultDataKey },
-                    options: { childDropAction: 'embed', title: 'Global Group Database' },
+                    options: { childDragAction: 'embed', title: 'Global Group Database' },
                 },
             ],
             [
@@ -1066,12 +1066,7 @@ export namespace Docs {
         }
 
         export function PileDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
-            return InstanceFromProto(
-                Prototypes.get(DocumentType.COL),
-                new List(documents),
-                { enableDragWhenActive: true, _forceActive: true, _freeform_noZoom: true, _freeform_noAutoPan: true, ...options, _type_collection: CollectionViewType.Pile },
-                id
-            );
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { dropAction: 'move', _forceActive: true, _freeform_noZoom: true, _freeform_noAutoPan: true, ...options, _type_collection: CollectionViewType.Pile }, id);
         }
 
         export function LinearDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
@@ -1523,7 +1518,7 @@ export namespace DocUtils {
         !simpleMenu &&
             ContextMenu.Instance.addItem({
                 description: 'Quick Notes',
-                subitems: DocListCast((Doc.UserDoc()['template-notes'] as Doc).data).map((note, i) => ({
+                subitems: DocListCast((Doc.UserDoc()['template_notes'] as Doc).data).map((note, i) => ({
                     description: ':' + StrCast(note.title),
                     event: undoable((args: { x: number; y: number }) => {
                         const textDoc = Docs.Create.TextDocument('', {
@@ -1583,10 +1578,10 @@ export namespace DocUtils {
     }
     export function findTemplate(templateName: string, type: string, signature: string) {
         let docLayoutTemplate: Opt<Doc>;
-        const iconViews = DocListCast(Cast(Doc.UserDoc()['template-icons'], Doc, null)?.data);
-        const templBtns = DocListCast(Cast(Doc.UserDoc()['template-buttons'], Doc, null)?.data);
-        const noteTypes = DocListCast(Cast(Doc.UserDoc()['template-notes'], Doc, null)?.data);
-        const clickFuncs = DocListCast(Cast(Doc.UserDoc()['template-clickFuncs'], Doc, null)?.data);
+        const iconViews = DocListCast(Cast(Doc.UserDoc()['template_icons'], Doc, null)?.data);
+        const templBtns = DocListCast(Cast(Doc.UserDoc()['template_buttons'], Doc, null)?.data);
+        const noteTypes = DocListCast(Cast(Doc.UserDoc()['template_notes'], Doc, null)?.data);
+        const clickFuncs = DocListCast(Cast(Doc.UserDoc()['template_clickFuncs'], Doc, null)?.data);
         const allTemplates = iconViews
             .concat(templBtns)
             .concat(noteTypes)
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 592f6d836..11a8dcaf6 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -97,7 +97,7 @@ export class CurrentUserUtils {
 
         const reqdOpts:DocumentOptions = {
             title: "Experimental Tools", _xMargin: 0, _layout_showTitle: "title", _chromeHidden: true,
-            _stayInCollection: true, _layout_hideContextMenu: true, _forceActive: true, isSystem: true,
+            _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, _forceActive: true, isSystem: true,
             _layout_autoHeight: true, _width: 500, _height: 300, _layout_fitWidth: true, _columnWidth: 35, ignoreClick: true, _lockedPosition: true,
         };
         const reqdScripts = { dropConverter : "convertToButtons(dragData)" };
@@ -123,7 +123,7 @@ export class CurrentUserUtils {
     }
 
     /// Initializes templates for editing click funcs of a document 
-    static setupClickEditorTemplates(doc: Doc, field = "template-clickFuncs") {
+    static setupClickEditorTemplates(doc: Doc, field = "template_clickFuncs") {
         const tempClicks = DocCast(doc[field]);
         const reqdClickOpts:DocumentOptions = { _width: 300, _height:200, isSystem: true};
         const reqdTempOpts:{opts:DocumentOptions, script: string}[] = [
@@ -144,7 +144,7 @@ export class CurrentUserUtils {
     }
 
     /// Initializes templates that can be applied to notes
-    static setupNoteTemplates(doc: Doc, field="template-notes") {
+    static setupNoteTemplates(doc: Doc, field="template_notes") {
         const tempNotes = DocCast(doc[field]);
         const reqdTempOpts:DocumentOptions[] = [
             { noteType: "Note", backgroundColor: "yellow", icon: "sticky-note"},
@@ -174,7 +174,7 @@ export class CurrentUserUtils {
     }
 
     // setup templates for different document types when they are iconified from Document Decorations
-    static setupDefaultIconTemplates(doc: Doc, field="template-icons") {
+    static setupDefaultIconTemplates(doc: Doc, field="template_icons") {
         const reqdOpts = { title: "icon templates", _height: 75, isSystem: true };
         const templateIconsDoc = DocUtils.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([], reqdOpts));
 
@@ -279,11 +279,11 @@ export class CurrentUserUtils {
             {key: "Script",     creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }},
             {key: "DataViz",    creator: opts => Docs.Create.DataVizDocument("/users/rz/Downloads/addresses.csv", opts),   opts: { _width: 300, _height: 300 }},
             {key: "Header",     creator: headerTemplate,                                    opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _layout_autoHeight: true, treeViewHideUnrendered: true}},
-            {key: "Trail",      creator: Docs.Create.PresDocument,                          opts: { _width: 400, _height: 30, _type_collection: CollectionViewType.Stacking, targetDropAction: "embed" as dropActionType, treeViewHideTitle: true, _layout_fitWidth:true, _chromeHidden: true, layout_boxShadow: "0 0" }},
+            {key: "Trail",      creator: Docs.Create.PresDocument,                          opts: { _width: 400, _height: 30, _type_collection: CollectionViewType.Stacking, dropAction: "embed" as dropActionType, treeViewHideTitle: true, _layout_fitWidth:true, _chromeHidden: true, layout_boxShadow: "0 0" }},
             {key: "Tab",        creator: opts => Docs.Create.FreeformDocument([], opts),    opts: { _width: 500, _height: 800, _layout_fitWidth: true, _freeform_backgroundGrid: true, }},
             {key: "Slide",      creator: opts => Docs.Create.TreeDocument([], opts),        opts: { _width: 300, _height: 200, _type_collection: CollectionViewType.Tree, 
                                                                                                 treeViewHasOverlay: true, _text_fontSize: "20px", _layout_autoHeight: true, 
-                                                                                                allowOverlayDrop: true, treeViewType: TreeViewType.outline, 
+                                                                                                dropAction:'move', treeViewType: TreeViewType.outline, 
                                                                                                 backgroundColor: "white", _xMargin: 0, _yMargin: 0, _createDocOnCR: true 
                                                                                             }, funcs: {title: 'self.text?.Text'}},
             ];
@@ -322,17 +322,16 @@ export class CurrentUserUtils {
         const creatorBtns = CurrentUserUtils.creatorBtnDescriptors(doc).map((reqdOpts) => {
             const btn = dragCreatorDoc ? DocListCast(dragCreatorDoc.data).find(doc => doc.title === reqdOpts.title): undefined;
             const opts:DocumentOptions = {...OmitKeys(reqdOpts, ["funcs", "scripts", "backgroundColor"]).omit, 
-                _width: 35, _height: 35, _layout_hideContextMenu: true, _stayInCollection: true, 
+                _width: 35, _height: 35, _layout_hideContextMenu: true, _dragOnlyWithinContainer: true, 
                 btnType: ButtonType.ToolButton, backgroundColor: reqdOpts.backgroundColor ?? Colors.DARK_GRAY, color: Colors.WHITE, isSystem: true,
-                _removeDropProperties: new List<string>(["_stayInCollection"]),
                 };
             return DocUtils.AssignScripts(DocUtils.AssignOpts(btn, opts) ?? Docs.Create.FontIconDocument(opts), reqdOpts.scripts, reqdOpts.funcs);
         });
 
         const reqdOpts:DocumentOptions = {
-            title: "Basic Item Creators", _layout_showTitle: "title", _xMargin: 0, _stayInCollection: true, _layout_hideContextMenu: true, _chromeHidden: true, isSystem: true,
+            title: "Basic Item Creators", _layout_showTitle: "title", _xMargin: 0, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, _chromeHidden: true, isSystem: true,
             _layout_autoHeight: true, _width: 500, _height: 300, _layout_fitWidth: true, _columnWidth: 40, ignoreClick: true, _lockedPosition: true, _forceActive: true,
-             childDropAction: 'embed' 
+             childDragAction: 'embed' 
         };
         const reqdScripts = { dropConverter: "convertToButtons(dragData)" };
         return DocUtils.AssignScripts(DocUtils.AssignOpts(dragCreatorDoc, reqdOpts, creatorBtns) ?? Docs.Create.MasonryDocument(creatorBtns, reqdOpts), reqdScripts);
@@ -357,7 +356,7 @@ export class CurrentUserUtils {
 
     /// the empty panel that is filled with whichever left menu button's panel has been selected
     static setupLeftSidebarPanel(doc: Doc, field="myLeftSidebarPanel") {
-        DocUtils.AssignDocField(doc, field, (opts) => ((doc:Doc) => {doc.isSystem = true; return doc;})(new Doc()), {isSystem:true});
+        DocUtils.AssignDocField(doc, field, (opts) => Doc.assign(new Doc(), opts as any), {isSystem:true, undoIgnoreFields: new List<string>(['proto'])});
     }
 
     /// Initializes the left sidebar menu buttons and the panels they open up
@@ -367,15 +366,14 @@ export class CurrentUserUtils {
         const menuBtns = CurrentUserUtils.leftSidebarMenuBtnDescriptions(doc).map(({ title, target, icon, scripts, funcs }) => {
             const btnDoc = myLeftSidebarMenu ? DocListCast(myLeftSidebarMenu.data).find(doc => doc.title === title) : undefined;
             const reqdBtnOpts:DocumentOptions = {
-                title, icon, target, btnType: ButtonType.MenuButton, isSystem: true, dontUndo: true, dontRegisterView: true,
-                _width: 60, _height: 60,  _stayInCollection: true, _layout_hideContextMenu: true,
-                _removeDropProperties: new List<string>(["_stayInCollection"]),
+                title, icon, target, btnType: ButtonType.MenuButton, isSystem: true, undoIgnoreFields: new List<string>(['height', 'data_columnHeaders']), dontRegisterView: true,
+                _width: 60, _height: 60,  _dragOnlyWithinContainer: true, _layout_hideContextMenu: true,
             };
             return DocUtils.AssignScripts(DocUtils.AssignOpts(btnDoc, reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), scripts, funcs);
         });
 
         const reqdStackOpts:DocumentOptions ={
-            title: "menuItemPanel", childDropAction: "same", backgroundColor: Colors.DARK_GRAY, layout_boxShadow: "rgba(0,0,0,0)",  dontRegisterView: true, ignoreClick: true,
+            title: "menuItemPanel", childDragAction: "same", backgroundColor: Colors.DARK_GRAY, layout_boxShadow: "rgba(0,0,0,0)",  dontRegisterView: true, ignoreClick: true,
             _chromeHidden: true, _gridGap: 0, _yMargin: 0, _yPadding: 0, _xMargin: 0, _layout_autoHeight: false, _width: 60, _columnWidth: 60, _lockedPosition: true, isSystem: true,
         };
         return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdStackOpts, menuBtns, { dropConverter: "convertToButtons(dragData)" });
@@ -458,7 +456,7 @@ export class CurrentUserUtils {
     /// Search option on the left side button panel
     static setupSearcher(doc: Doc, field:string) {
         return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.SearchDocument(opts), {
-                dontRegisterView: true, backgroundColor: "dimgray", ignoreClick: true, title: "Search Panel", isSystem: true, childDropAction: "embed", 
+                dontRegisterView: true, backgroundColor: "dimgray", ignoreClick: true, title: "Search Panel", isSystem: true, childDragAction: "embed", 
                 _lockedPosition: true, _type_collection: CollectionViewType.Schema  });
     }
 
@@ -469,7 +467,7 @@ export class CurrentUserUtils {
         const templateBtns = CurrentUserUtils.setupExperimentalTemplateButtons(doc,DocListCast(myTools?.data)?.length > 1 ? DocListCast(myTools.data)[1]:undefined);
         const reqdToolOps:DocumentOptions = {
             title: "My Tools", isSystem: true, ignoreClick: true, layout_boxShadow: "0 0",
-            _layout_showTitle: "title", _width: 500, _yMargin: 20, _lockedPosition: true, _forceActive: true, _stayInCollection: true, _layout_hideContextMenu: true, _chromeHidden: true, 
+            _layout_showTitle: "title", _width: 500, _yMargin: 20, _lockedPosition: true, _forceActive: true, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, _chromeHidden: true, 
         };
         return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdToolOps, [creatorBtns, templateBtns]);
     }
@@ -481,7 +479,7 @@ export class CurrentUserUtils {
         const toggleDarkTheme = `this.colorScheme = this.colorScheme ? undefined : "${ColorScheme.Dark}"`;
         const newDashboard = `createNewDashboard()`;
 
-        const reqdBtnOpts:DocumentOptions = { _forceActive: true,  _width: 30, _height: 30, _stayInCollection: true, _layout_hideContextMenu: true,
+        const reqdBtnOpts:DocumentOptions = { _forceActive: true,  _width: 30, _height: 30, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true,
              title: "new dashboard", btnType: ButtonType.ClickButton, toolTip: "Create new dashboard", buttonText: "New trail", icon: "plus", isSystem: true };
         const reqdBtnScript = {onClick: newDashboard,}
         const newDashboardButton = DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(myDashboards?.layout_headerButton), reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), reqdBtnScript);
@@ -495,8 +493,8 @@ export class CurrentUserUtils {
         const childContextMenuIcons = ["chalkboard", "tv", "camera", "users", "times", "trash"]; // entries must be kept in synch with childContextMenuScripts, childContextMenuLabels, and childContextMenuFilters
         const reqdOpts:DocumentOptions = {
             title: "My Dashboards", childHideLinkButton: true, treeViewFreezeChildren: "remove|add", treeViewHideTitle: true, layout_boxShadow: "0 0", childDontRegisterViews: true,
-            targetDropAction: "same", treeViewType: TreeViewType.fileSystem, isFolder: true, isSystem: true,  treeViewTruncateTitleWidth: 350, ignoreClick: true, 
-            layout_headerButton: newDashboardButton, childDropAction: "embed",
+            dropAction: "same", treeViewType: TreeViewType.fileSystem, isFolder: true, isSystem: true,  treeViewTruncateTitleWidth: 350, ignoreClick: true, 
+            layout_headerButton: newDashboardButton, childDragAction: "embed",
             _layout_showTitle: "title", _height: 400, _gridGap: 5, _forceActive: true, _lockedPosition: true,
             contextMenuLabels:new List<string>(contextMenuLabels),
             contextMenuIcons:new List<string>(contextMenuIcons),
@@ -520,20 +518,20 @@ export class CurrentUserUtils {
     /// initializes the left sidebar File system pane
     static setupFilesystem(doc: Doc, field:string) {
         var myFilesystem = DocCast(doc[field]);
-        const myFileOrphans = DocUtils.AssignDocField(doc, "myFileOrphans", (opts) => Docs.Create.TreeDocument([], opts), { title: "Unfiled", _stayInCollection: true, isSystem: true, isFolder: true });
+        const myFileOrphans = DocUtils.AssignDocField(doc, "myFileOrphans", (opts) => Docs.Create.TreeDocument([], opts), { title: "Unfiled", undoIgnoreFields:new List<string>(['treeViewSortCriterion']), _dragOnlyWithinContainer: true, isSystem: true, isFolder: true });
         
         const newFolder = `TreeView_addNewFolder()`;
         const newFolderOpts: DocumentOptions = {
-            _forceActive: true, _stayInCollection: true, _layout_hideContextMenu: true, _width: 30, _height: 30,
+            _forceActive: true, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, _width: 30, _height: 30, undoIgnoreFields:new List<string>(['treeViewSortCriterion']),
             title: "New folder", btnType: ButtonType.ClickButton, toolTip: "Create new folder", buttonText: "New folder", icon: "folder-plus", isSystem: true
         };
         const newFolderScript = { onClick: newFolder};
         const newFolderButton = DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(myFilesystem?.layout_headerButton), newFolderOpts) ?? Docs.Create.FontIconDocument(newFolderOpts), newFolderScript);
         
         const reqdOpts:DocumentOptions = { _layout_showTitle: "title", _height: 100, _gridGap: 5, _forceActive: true, _lockedPosition: true, 
-            title: "My Documents", layout_headerButton: newFolderButton, treeViewHideTitle: true, targetDropAction: "proto", isSystem: true,
+            title: "My Documents", layout_headerButton: newFolderButton, treeViewHideTitle: true, dropAction: "proto", isSystem: true,
             isFolder: true, treeViewType: TreeViewType.fileSystem, childHideLinkButton: true, layout_boxShadow: "0 0", childDontRegisterViews: true, 
-            treeViewTruncateTitleWidth: 350, ignoreClick: true, childDropAction: "embed",
+            treeViewTruncateTitleWidth: 350, ignoreClick: true, childDragAction: "embed",
             childContextMenuLabels: new List<string>(["Create new folder"]),
             childContextMenuIcons: new List<string>(["plus"]),
             layout_explainer: "This is your file manager where you can create folders to keep track of documents independently of your dashboard."
@@ -549,8 +547,8 @@ export class CurrentUserUtils {
     /// initializes the panel displaying docs that have been recently closed
     static setupRecentlyClosed(doc: Doc, field:string) {
         const reqdOpts:DocumentOptions = { _layout_showTitle: "title", _lockedPosition: true, _gridGap: 5, _forceActive: true, 
-            title: "My Recently Closed", childHideLinkButton: true, treeViewHideTitle: true, childDropAction: "embed", isSystem: true,
-            treeViewTruncateTitleWidth: 350, ignoreClick: true,  layout_boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same",
+            title: "My Recently Closed", childHideLinkButton: true, treeViewHideTitle: true, childDragAction: "embed", isSystem: true,
+            treeViewTruncateTitleWidth: 350, ignoreClick: true,  layout_boxShadow: "0 0", childDontRegisterViews: true, dropAction: "same",
             contextMenuLabels: new List<string>(["Empty recently closed"]),
             contextMenuIcons:new List<string>(["trash"]),
             layout_explainer: "Recently closed documents appear in this menu. They will only be deleted if you explicity empty this list."
@@ -558,7 +556,7 @@ export class CurrentUserUtils {
         const recentlyClosed = DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), reqdOpts);
 
         const clearAll = (target:string) => `getProto(${target}).data = new List([])`;
-        const clearBtnsOpts:DocumentOptions = { _width: 30, _height: 30, _forceActive: true, _stayInCollection: true, _layout_hideContextMenu: true, 
+        const clearBtnsOpts:DocumentOptions = { _width: 30, _height: 30, _forceActive: true, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, 
             title: "Empty", target: recentlyClosed, btnType: ButtonType.ClickButton,  buttonText: "Empty", icon: "trash", isSystem: true,
             toolTip: "Empty recently closed",};
         DocUtils.AssignDocField(recentlyClosed, "layout_headerButton", (opts) => Docs.Create.FontIconDocument(opts), clearBtnsOpts, undefined, {onClick: clearAll("self.target")});
@@ -575,7 +573,7 @@ export class CurrentUserUtils {
     static setupUserDocView(doc: Doc, field:string) {
         const reqdOpts:DocumentOptions = {
             _lockedPosition: true, _gridGap: 5, _forceActive: true, title: Doc.CurrentUserEmail +"-view",
-            layout_boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", ignoreClick: true, isSystem: true,
+            layout_boxShadow: "0 0", childDontRegisterViews: true, dropAction: "same", ignoreClick: true, isSystem: true,
             treeViewHideTitle: true, treeViewTruncateTitleWidth: 350
         };
         if (!doc[field]) DocUtils.AssignOpts(doc, {treeViewOpen: true, treeViewExpandedView: "fields" });
@@ -590,7 +588,7 @@ export class CurrentUserUtils {
 
     static createToolButton = (opts: DocumentOptions) => Docs.Create.FontIconDocument({
         btnType: ButtonType.ToolButton, _forceActive: true, _layout_hideContextMenu: true, 
-        _removeDropProperties: new List<string>([ "_layout_hideContextMenu", "stayInCollection"]), 
+        _dropPropertiesToRemove: new List<string>([ "_layout_hideContextMenu"]), 
         _nativeWidth: 40, _nativeHeight: 40, _width: 40, _height: 40, isSystem: true, ...opts,
     })
 
@@ -608,9 +606,9 @@ export class CurrentUserUtils {
             { scripts: { },                       opts: { title: "linker",            layout: "<LinkingUI>",          toolTip: "link started"}},
             { scripts: { },                       opts: { title: "currently playing", layout: "<CurrentlyPlayingUI>", toolTip: "currently playing media"}},
         ];
-        const btns = btnDescs.map(desc => dockBtn({_width: 30, _height: 30, defaultDoubleClick: 'ignore', dontUndo: true, _stayInCollection: true, ...desc.opts}, desc.scripts));
+        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", childDropAction: 'embed',
+            title: "docked buttons", _height: 40, flexGap: 0, layout_boxShadow: "standard", childDragAction: 'move',
             childDontRegisterViews: true, linearView_IsExpanded: 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 });
@@ -716,12 +714,11 @@ export class CurrentUserUtils {
         const reqdOpts:DocumentOptions = {
             ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit,
             backgroundColor: params.backgroundColor ??"transparent", ///  a bit hacky. if an onClick is specified, then assume a toggle uses onClick to get the backgroundColor (see below).  Otherwise, assume a transparent background
-            color: Colors.WHITE, isSystem: true, //dontUndo: true, 
+            color: Colors.WHITE, isSystem: true,
             _nativeWidth: params.width ?? 30, _width: params.width ?? 30,
             _height: 30, _nativeHeight: 30, linearBtnWidth: params.linearBtnWidth,
             toolType: params.toolType, expertMode: params.expertMode,
-            _stayInCollection: true, _layout_hideContextMenu: true, _lockedPosition: true,
-            _removeDropProperties: new List<string>([ "_stayInCollection"]),
+            _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, _lockedPosition: true,
         };
         const reqdFuncs:{[key:string]:any} = {
             ...params.funcs,
@@ -732,14 +729,14 @@ export class CurrentUserUtils {
 
     /// Initializes all the default buttons for the top bar context menu
     static setupContextMenuButtons(doc: Doc, field="myContextMenuBtns") {
-        const reqdCtxtOpts:DocumentOptions = { title: "context menu buttons", dontUndo:true, flexGap: 0, childDropAction: 'embed', childDontRegisterViews: true,  linearView_IsExpanded: true, ignoreClick: true, linearView_Expandable: false, _height: 35 };
+        const reqdCtxtOpts:DocumentOptions = { title: "context menu buttons", undoIgnoreFields:new List<string>(['width', "linearView_IsExpanded"]), flexGap: 0, childDragAction: 'embed', childDontRegisterViews: true,  linearView_IsExpanded: 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 => {
             const menuBtnDoc = DocListCast(ctxtMenuBtnsDoc?.data).find(doc => doc.title === params.title);
             if (!params.subMenu) {
                 return this.setupContextMenuButton(params, menuBtnDoc);
             } else {
-                const reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, dontUndo: true,
+                const reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, undoIgnoreFields: new List<string>(['width', "linearView_IsExpanded"]),
                     childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: params.scripts?.onClick ? false : true,
                     linearView_SubMenu: true, linearView_Expandable: true, };
                 const items = params.subMenu?.map(sub =>
@@ -786,13 +783,13 @@ export class CurrentUserUtils {
         const sharedDocOpts:DocumentOptions = {
             title: "My Shared Docs",
             userColor: "rgb(202, 202, 202)",
-            isFolder:true,
+            isFolder:true, undoIgnoreFields:new List<string>(['treeViewSortCriterion']),
             childContextMenuFilters: new List<ScriptField>([dashboardFilter!,]),
             childContextMenuScripts: new List<ScriptField>([addToDashboards!,]),
             childContextMenuLabels: new List<string>(["Add to Dashboards",]),
             childContextMenuIcons: new List<string>(["user-plus",]),
             "acl-Public": SharingPermissions.Augment, "_acl-Public": SharingPermissions.Augment,
-            childDropAction: "embed", isSystem: true, contentPointerEvents: "all", childLimitHeight: 0, _yMargin: 0, _gridGap: 15, childDontRegisterViews:true,
+            childDragAction: "embed", isSystem: true, contentPointerEvents: "all", childLimitHeight: 0, _yMargin: 0, _gridGap: 15, childDontRegisterViews:true,
             // NOTE: treeViewHideTitle & _layout_showTitle is for a TreeView's editable title, _layout_showTitle is for DocumentViews title bar
             _layout_showTitle: "title", treeViewHideTitle: true, ignoreClick: true, _lockedPosition: true, layout_boxShadow: "0 0", _chromeHidden: true, dontRegisterView: true, 
             layout_explainer: "This is where documents or dashboards that other users have shared with you will appear. To share a document or dashboard right click and select 'Share'"
@@ -805,14 +802,14 @@ export class CurrentUserUtils {
     static setupImportSidebar(doc: Doc, field:string) {   
         const reqdOpts:DocumentOptions = {
             title: "My Imports", _forceActive: true, ignoreClick: true, _layout_showTitle: "title", 
-            _stayInCollection: true, _layout_hideContextMenu: true, childLimitHeight: 0,
-            childDropAction: "copy", _layout_autoHeight: true, _yMargin: 50, _gridGap: 15, layout_boxShadow: "0 0", _lockedPosition: true, isSystem: true, _chromeHidden: true,
+            _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, childLimitHeight: 0,
+            childDragAction: "copy", _layout_autoHeight: true, _yMargin: 50, _gridGap: 15, layout_boxShadow: "0 0", _lockedPosition: true, isSystem: true, _chromeHidden: true,
             dontRegisterView: true, layout_explainer: "This is where documents that are Imported into Dash will go."
         };
         const myImports = DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.StackingDocument([], opts), reqdOpts);
     
         const reqdBtnOpts:DocumentOptions = { _forceActive: true, toolTip: "Import from computer",
-            _width: 30, _height: 30, _stayInCollection: true, _layout_hideContextMenu: true, title: "Import", btnType: ButtonType.ClickButton, 
+            _width: 30, _height: 30, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, title: "Import", btnType: ButtonType.ClickButton, 
             buttonText: "Import", icon: "upload", isSystem: true };
         DocUtils.AssignDocField(myImports, "layout_headerButton", (opts) => Docs.Create.FontIconDocument(opts), reqdBtnOpts, undefined, { onClick: "importDocument()" });
         return myImports;
@@ -864,7 +861,7 @@ export class CurrentUserUtils {
         this.setupDocTemplates(doc); // sets up the template menu of templates
         this.setupFieldInfos(doc); // sets up the collection of field info descriptions for each possible DocumentOption
         DocUtils.AssignDocField(doc, "globalScriptDatabase", (opts) =>  Docs.Prototypes.MainScriptDocument(), {});
-        DocUtils.AssignDocField(doc, "myHeaderBar", (opts) => Docs.Create.MulticolumnDocument([], opts), { title: "header bar", isSystem: true }); // drop down panel at top of dashboard for stashing documents
+        DocUtils.AssignDocField(doc, "myHeaderBar", (opts) => Docs.Create.MulticolumnDocument([], opts), { title: "header bar", isSystem: true, childDocumentsActive:false, dropAction: 'move'}); // drop down panel at top of dashboard for stashing documents
         
         Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MySharedDocs)
 
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 87833d322..85101fcab 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -15,42 +15,36 @@ import { SelectionManager } from './SelectionManager';
 import { SnappingManager } from './SnappingManager';
 import { UndoManager } from './UndoManager';
 
-export type dropActionType = 'embed' | 'copy' | 'move' | 'same' | 'proto' | 'none' | undefined; // undefined = move, "same" = move but don't call removeDropProperties
+export type dropActionType = 'embed' | 'copy' | 'move' | 'same' | 'proto' | 'none' | undefined; // undefined = move, "same" = move but don't call dropPropertiesToRemove
 
 /**
  * Initialize drag
  * @param _reference: The HTMLElement that is being dragged
  * @param docFunc: The Dash document being moved
- * @param moveFunc: The function called when the document is moved
- * @param dropAction: What to do with the document when it is dropped
- * @param dragStarted: Method to call when the drag is started
  */
-export function SetupDrag(_reference: React.RefObject<HTMLElement>, docFunc: () => Doc | Promise<Doc | undefined> | undefined, moveFunc?: DragManager.MoveFunction, dropAction?: dropActionType, dragStarted?: () => void) {
-    const onRowMove = async (e: PointerEvent) => {
+export function SetupDrag(_reference: React.RefObject<HTMLElement>, docFunc: () => Doc | undefined) {
+    const onRowMove = (e: PointerEvent) => {
         e.stopPropagation();
         e.preventDefault();
 
         document.removeEventListener('pointermove', onRowMove);
         document.removeEventListener('pointerup', onRowUp);
-        const doc = await docFunc();
+        const doc = docFunc();
         if (doc) {
             const dragData = new DragManager.DocumentDragData([doc]);
-            dragData.dropAction = dropAction;
-            dragData.moveDocument = moveFunc;
             DragManager.StartDocumentDrag([_reference.current!], dragData, e.x, e.y);
-            dragStarted?.();
         }
     };
     const onRowUp = (): void => {
         document.removeEventListener('pointermove', onRowMove);
         document.removeEventListener('pointerup', onRowUp);
     };
-    const onItemDown = async (e: React.PointerEvent) => {
+    const onItemDown = (e: React.PointerEvent) => {
         if (e.button === 0) {
             e.stopPropagation();
             if (e.shiftKey) {
                 e.persist();
-                const dragDoc = await docFunc();
+                const dragDoc = docFunc();
                 dragDoc && DragManager.StartWindowDrag?.(e, [dragDoc]);
             } else {
                 document.addEventListener('pointermove', onRowMove);
@@ -132,7 +126,7 @@ export namespace DragManager {
         userDropAction: dropActionType; // the user requested drop action -- this will be honored as specified by modifier keys
         defaultDropAction?: dropActionType; // an optionally specified default drop action when there is no user drop actionl - this will be honored if there is no user drop action
         dropAction: dropActionType; // a drop action request by the initiating code.  the actual drop action may be different -- eg, if the request is 'embed', but the document is dropped within the same collection, the drop action will be switched to 'move'
-        removeDropProperties?: string[];
+        dropPropertiesToRemove?: string[];
         moveDocument?: MoveFunction;
         removeDocument?: RemoveFunction;
         isDocDecorationMove?: boolean; // Flags that Document decorations are used to drag document which allows suppression of onDragStart scripts
@@ -189,7 +183,7 @@ export namespace DragManager {
         const handler = (e: Event) => dropFunc(e, (e as CustomEvent<DropEvent>).detail);
         const preDropHandler = (e: Event) => {
             const de = (e as CustomEvent<DropEvent>).detail;
-            (preDropFunc ?? defaultPreDropFunc)(e, de, StrCast(doc?.targetDropAction) as dropActionType);
+            (preDropFunc ?? defaultPreDropFunc)(e, de, StrCast(doc?.dropAction) as dropActionType);
         };
         element.addEventListener('dashOnDrop', handler);
         doc && element.addEventListener('dashPreDrop', preDropHandler);
@@ -230,11 +224,13 @@ export namespace DragManager {
                     )
                 ).filter(d => d);
                 !['same', 'proto'].includes(docDragData.dropAction as any) &&
-                    docDragData.droppedDocuments.forEach((drop: Doc, i: number) => {
-                        const dragProps = StrListCast(dragData.draggedDocuments[i].removeDropProperties);
-                        const remProps = (dragData?.removeDropProperties || []).concat(Array.from(dragProps));
-                        remProps.map(prop => (drop[prop] = undefined));
-                    });
+                    docDragData.droppedDocuments
+                        // .filter(drop => !drop.dragOnlyWithinContainer || ['embed', 'copy'].includes(docDragData.dropAction as any))
+                        .forEach((drop: Doc, i: number) => {
+                            const dragProps = StrListCast(dragData.draggedDocuments[i].dropPropertiesToRemove);
+                            const remProps = (dragData?.dropPropertiesToRemove || []).concat(Array.from(dragProps));
+                            [...remProps, 'dropPropertiesToRemove'].map(prop => (drop[prop] = undefined));
+                        });
             }
             return e;
         };
@@ -586,6 +582,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) {
         const dropArgs = {
+            cancelable: true, // allows preventDefault() to be called to cancel the drop
             bubbles: true,
             detail: {
                 ...pos,
@@ -598,8 +595,9 @@ 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);
-        target.dispatchEvent(new CustomEvent<DropEvent>('dashOnDrop', dropArgs));
+        UndoManager.EndTempBatch(target.dispatchEvent(new CustomEvent<DropEvent>('dashOnDrop', dropArgs))); // event return val is true unless the event preventDefault() is called
         options?.dragComplete?.(complete);
         endDrag?.();
     }
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index f46ea393a..47997cc5c 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -57,11 +57,11 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
         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')) {
-            if (data.removeDropProperties || dbox.removeDropProperties) {
+            if (data.dropPropertiesToRemove || dbox.dropPropertiesToRemove) {
                 //dbox = Doc.MakeEmbedding(doc);  // don't need to do anything if dropping an icon doc onto an icon bar since there should be no layout data for an icon
                 dbox = Doc.MakeEmbedding(dbox);
-                const dragProps = Cast(dbox.removeDropProperties, listSpec('string'), []);
-                const remProps = (data.removeDropProperties || []).concat(Array.from(dragProps));
+                const dragProps = Cast(dbox.dropPropertiesToRemove, listSpec('string'), []);
+                const remProps = (data.dropPropertiesToRemove || []).concat(Array.from(dragProps));
                 remProps.map(prop => (dbox[prop] = undefined));
             }
         } else if (!doc.onDragStart && !doc.isButtonBar) {
@@ -81,7 +81,7 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
                 icon: layoutDoc.isTemplateDoc ? 'font' : 'bolt',
             });
             dbox.dragFactory = layoutDoc;
-            dbox.removeDropProperties = doc.removeDropProperties instanceof ObjectField ? ObjectField.MakeCopy(doc.removeDropProperties) : undefined;
+            dbox.dropPropertiesToRemove = doc.dropPropertiesToRemove instanceof ObjectField ? ObjectField.MakeCopy(doc.dropPropertiesToRemove) : undefined;
             dbox.onDragStart = ScriptField.MakeFunction('makeDelegate(this.dragFactory)');
         } else if (doc.isButtonBar) {
             dbox.ignoreClick = true;
diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts
index 6fef9d660..2379cb2ab 100644
--- a/src/client/util/UndoManager.ts
+++ b/src/client/util/UndoManager.ts
@@ -91,7 +91,7 @@ export namespace UndoManager {
     let currentBatch: UndoBatch | undefined;
     export let batchCounter = observable.box(0);
     let undoing = false;
-    let tempEvents: UndoEvent[] | undefined = undefined;
+    export let tempEvents: UndoEvent[] | undefined = undefined;
 
     export function AddEvent(event: UndoEvent, value?: any): void {
         if (currentBatch && batchCounter.get() && !undoing) {
@@ -187,15 +187,11 @@ export namespace UndoManager {
         return false;
     });
 
-    export function RunInTempBatch<T>(fn: () => T) {
+    export function StartTempBatch() {
         tempEvents = [];
-        try {
-            const success = runInAction(fn);
-            if (!success) UndoManager.UndoTempBatch();
-            return success;
-        } finally {
-            tempEvents = undefined;
-        }
+    }
+    export function EndTempBatch<T>(success: boolean) {
+        UndoManager.UndoTempBatch(success);
     }
     //TODO Make this return the return value
     export function RunInBatch<T>(fn: () => T, batchName: string) {
@@ -206,10 +202,11 @@ export namespace UndoManager {
             batch.end();
         }
     }
-    export const UndoTempBatch = action(() => {
-        if (tempEvents) {
+    export const UndoTempBatch = action((success: any) => {
+        if (tempEvents && !success) {
             undoing = true;
             for (let i = tempEvents.length - 1; i >= 0; i--) {
+                currentBatch?.includes(tempEvents[i]) && currentBatch.splice(currentBatch.indexOf(tempEvents[i]));
                 tempEvents[i].undo();
             }
             undoing = false;
diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx
index 50cf2226e..6aae302ac 100644
--- a/src/client/views/DashboardView.tsx
+++ b/src/client/views/DashboardView.tsx
@@ -389,7 +389,7 @@ export class DashboardView extends React.Component {
             _forceActive: true,
             _width: 30,
             _height: 30,
-            _stayInCollection: true,
+            _dragOnlyWithinContainer: true,
             _layout_hideContextMenu: true,
             title: 'New trail',
             toolTip: 'Create new trail',
@@ -412,7 +412,7 @@ export class DashboardView extends React.Component {
             _layout_fitWidth: true,
             _gridGap: 5,
             _forceActive: true,
-            childDropAction: 'embed',
+            childDragAction: 'embed',
             treeViewTruncateTitleWidth: 150,
             ignoreClick: true,
             layout_headerButton: myTrailsBtn,
@@ -421,7 +421,7 @@ export class DashboardView extends React.Component {
             _lockedPosition: true,
             layout_boxShadow: '0 0',
             childDontRegisterViews: true,
-            targetDropAction: 'same',
+            dropAction: 'same',
             isSystem: true,
             layout_explainer: 'All of the trails that you have created will appear here.',
         };
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index db24229dc..0709d6cb9 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -172,8 +172,8 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
                 return true;
             }
             const first = doc instanceof Doc ? doc : doc[0];
-            if (!first?._stayInCollection && addDocument !== returnFalse) {
-                return UndoManager.RunInTempBatch(() => this.removeDocument(doc, annotationKey, true) && addDocument(doc, annotationKey));
+            if (!first?._dragOnlyWithinContainer && addDocument !== returnFalse) {
+                return this.removeDocument(doc, annotationKey, true) && addDocument(doc, annotationKey);
             }
             return false;
         };
@@ -213,7 +213,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
                         .map(doc => {
                             // only make a pushpin if we have acl's to edit the document
                             //DocUtils.LeavePushpin(doc);
-                            doc._stayInCollection = undefined;
+                            doc._dragOnlyWithinContainer = undefined;
                             doc.embedContainer = this.props.Document;
                             if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.rootDoc;
 
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 528b82e3e..41ce08d3c 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -603,7 +603,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                     let actualdH = Math.max(docheight + dH * scale, 20);
                     let dX = !dWin ? 0 : scale * refCent[0] * (1 - (1 + dWin / refWidth));
                     let dY = !dHin ? 0 : scale * refCent[1] * (1 - (1 + dHin / refHeight));
-                    const preserveNativeDim = doc._nativeHeightUnfrozen === false && doc._nativeDimModifiable === false;
+                    const preserveNativeDim = !doc._nativeHeightUnfrozen && !doc._nativeDimModifiable;
                     const fixedAspect = nwidth && nheight && (!doc._layout_fitWidth || preserveNativeDim || e.ctrlKey || doc.nativeHeightUnfrozen || doc.nativeDimModifiable);
                     if (fixedAspect) {
                         if ((Math.abs(dW) > Math.abs(dH) && ((!dragBottom && !dragTop) || !modifyNativeDim)) || dragRight) {
@@ -636,7 +636,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                                 }
                             }
                             if (!modifyNativeDim) {
-                                actualdH = (nheight / nwidth) * docwidth; //, actualdH);
+                                actualdH = (nheight / nwidth) * NumCast(doc._width); //, actualdH);
                             }
                             doc._height = actualdH;
                         }
@@ -756,7 +756,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
             hideDecorations ||
             seldocview.props.hideOpenButton ||
             seldocview.rootDoc.layout_hideOpenButton ||
-            SelectionManager.Views().some(docView => docView.rootDoc._stayInCollection || docView.rootDoc.isGroup || docView.rootDoc.layout_hideOpenButton) ||
+            SelectionManager.Views().some(docView => docView.rootDoc._dragOnlyWithinContainer || docView.rootDoc.isGroup || docView.rootDoc.layout_hideOpenButton) ||
             this._isRounding ||
             this._isRotating;
         const hideDeleteButton =
@@ -767,7 +767,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
             seldocview.rootDoc.hideDeleteButton ||
             SelectionManager.Views().some(docView => {
                 const collectionAcl = docView.props.docViewPath()?.lastElement() ? GetEffectiveAcl(docView.props.docViewPath().lastElement().rootDoc[DocData]) : AclEdit;
-                return (docView.rootDoc.stayInCollection && !docView.rootDoc._isTimelineLabel) || (collectionAcl !== AclAdmin && collectionAcl !== AclEdit && GetEffectiveAcl(docView.rootDoc) !== AclAdmin);
+                return collectionAcl !== AclAdmin && collectionAcl !== AclEdit && GetEffectiveAcl(docView.rootDoc) !== AclAdmin;
             });
 
         const topBtn = (key: string, icon: string, pointerDown: undefined | ((e: React.PointerEvent) => void), click: undefined | ((e: any) => void), title: string) => (
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 0951bff22..54ad519de 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -4,8 +4,7 @@ import { action, computed, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, Opt } from '../../fields/Doc';
 import { InkData, InkTool } from '../../fields/InkField';
-import { ScriptField } from '../../fields/ScriptField';
-import { Cast, FieldValue, NumCast } from '../../fields/Types';
+import { NumCast } from '../../fields/Types';
 import MobileInkOverlay from '../../mobile/MobileInkOverlay';
 import { GestureUtils } from '../../pen-gestures/GestureUtils';
 import { MobileInkOverlayContent } from '../../server/Message';
@@ -35,7 +34,6 @@ import { InkTranscription } from './InkTranscription';
 import { checkInksToGroup } from './nodes/button/FontIconBox';
 import { DocumentView } from './nodes/DocumentView';
 import { RadialMenu } from './nodes/RadialMenu';
-import HorizontalPalette from './Palette';
 import { Touchable } from './Touchable';
 import TouchScrollableMenu, { TouchScrollableMenuItem } from './TouchScrollableMenu';
 
@@ -79,7 +77,6 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
     private _overlayRef = React.createRef<HTMLDivElement>();
     private _d1: Doc | undefined;
     private _inkToTextDoc: Doc | undefined;
-    private _thumbDoc: Doc | undefined;
     private thumbIdentifier?: number;
     private pointerIdentifier?: number;
     private _hands: Map<number, React.Touch[]> = new Map<number, React.Touch[]>();
@@ -93,78 +90,12 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
         GestureOverlay.Instances.push(this);
     }
 
-    static setupThumbButtons(doc: Doc) {
-        const docProtoData: { title: string; icon: string; drag?: string; toolType?: string; ignoreClick?: boolean; pointerDown?: string; pointerUp?: string; clipboard?: Doc; backgroundColor?: string; dragFactory?: Doc }[] = [
-            { title: 'use pen', icon: 'pen-nib', pointerUp: 'resetPen()', pointerDown: 'setPen(2, this.backgroundColor)', backgroundColor: 'blue' },
-            { title: 'use highlighter', icon: 'highlighter', pointerUp: 'resetPen()', pointerDown: 'setPen(20, this.backgroundColor)', backgroundColor: 'yellow' },
-            {
-                title: 'notepad',
-                icon: 'clipboard',
-                pointerUp: 'GestureOverlay.Instance.closeFloatingDoc()',
-                pointerDown: 'GestureOverlay.Instance.openFloatingDoc(this.clipboard)',
-                clipboard: Docs.Create.FreeformDocument([], { _width: 300, _height: 300, isSystem: true }),
-                backgroundColor: 'orange',
-            },
-            { title: 'interpret text', icon: 'font', toolType: 'inktotext', pointerUp: "setToolglass('none')", pointerDown: 'setToolglass(self.toolType)', backgroundColor: 'orange' },
-            { title: 'ignore gestures', icon: 'signature', toolType: 'ignoregesture', pointerUp: "setToolglass('none')", pointerDown: 'setToolglass(self.toolType)', backgroundColor: 'green' },
-        ];
-        return docProtoData.map(data =>
-            Docs.Create.FontIconDocument({
-                _nativeWidth: 10,
-                _nativeHeight: 10,
-                _width: 10,
-                _height: 10,
-                title: data.title,
-                icon: data.icon,
-                toolType: data.toolType,
-                _dropAction: data.pointerDown ? 'copy' : undefined,
-                ignoreClick: data.ignoreClick,
-                onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined,
-                clipboard: data.clipboard,
-                onPointerUp: data.pointerUp ? ScriptField.MakeScript(data.pointerUp) : undefined,
-                onPointerDown: data.pointerDown ? ScriptField.MakeScript(data.pointerDown) : undefined,
-                backgroundColor: data.backgroundColor,
-                dragFactory: data.dragFactory,
-                isSystem: true,
-            })
-        );
-    }
-
-    static setupThumbDoc(userDoc: Doc) {
-        if (!userDoc.thumbDoc) {
-            const thumbDoc = Docs.Create.LinearDocument(GestureOverlay.setupThumbButtons(userDoc), {
-                _width: 100,
-                _height: 50,
-                ignoreClick: true,
-                _lockedPosition: true,
-                title: 'buttons',
-                _layout_autoHeight: true,
-                _yMargin: 5,
-                linearView_IsExpanded: true,
-                backgroundColor: 'white',
-                isSystem: true,
-            });
-            thumbDoc.inkToTextDoc = Docs.Create.LinearDocument([], {
-                _width: 300,
-                _height: 25,
-                _layout_autoHeight: true,
-                linearView_IsExpanded: true,
-                flexDirection: 'column',
-                isSystem: true,
-            });
-            userDoc.thumbDoc = thumbDoc;
-        }
-        return Cast(userDoc.thumbDoc, Doc);
-    }
-
     componentWillUnmount() {
         GestureOverlay.Instances.splice(GestureOverlay.Instances.indexOf(this), 1);
         GestureOverlay.Instance = GestureOverlay.Instances.lastElement();
     }
     componentDidMount = () => {
         GestureOverlay.Instance = this;
-        this._thumbDoc = FieldValue(Cast(GestureOverlay.setupThumbDoc(Doc.UserDoc()), Doc));
-        this._inkToTextDoc = FieldValue(Cast(this._thumbDoc?.inkToTextDoc, Doc));
     };
 
     // TODO: nda - add dragging groups with one finger drag and have to click into group to scroll within the group
@@ -395,24 +326,6 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
 
         this.thumbIdentifier = thumb?.identifier;
         this._hands.set(thumb.identifier, fingers);
-        const others = fingers.filter(f => f !== thumb);
-        const minX = Math.min(...others.map(f => f.clientX));
-        const minY = Math.min(...others.map(f => f.clientY));
-
-        // load up the palette collection around the thumb
-        const thumbDoc = await Cast(GestureOverlay.setupThumbDoc(Doc.UserDoc()), Doc);
-        if (thumbDoc) {
-            runInAction(() => {
-                RadialMenu.Instance._display = false;
-                this._inkToTextDoc = FieldValue(Cast(thumbDoc.inkToTextDoc, Doc));
-                this._thumbDoc = thumbDoc;
-                this._thumbX = thumb.clientX;
-                this._thumbY = thumb.clientY;
-                this._menuX = thumb.clientX + 50;
-                this._menuY = thumb.clientY;
-                this._palette = <HorizontalPalette key="palette" x={minX} y={minY} thumb={[thumb.clientX, thumb.clientY]} thumbDoc={thumbDoc} />;
-            });
-        }
 
         this.removeMoveListeners();
         document.removeEventListener('touchmove', this.handleHandMove);
@@ -462,11 +375,6 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
                         if (Math.abs(pt.clientY - this._thumbY) > 10 * window.devicePixelRatio) {
                             this._selectedIndex = Math.min(Math.max(-1, -Math.ceil((pt.clientY - this._thumbY) / (10 * window.devicePixelRatio)) - 1), this._possibilities.length - 1);
                         }
-                    } else if (this._thumbDoc) {
-                        if (Math.abs(pt.clientX - this._thumbX) > 15 * window.devicePixelRatio) {
-                            this._thumbDoc.selectedIndex = Math.max(-1, NumCast(this._thumbDoc.selectedIndex) - Math.sign(pt.clientX - this._thumbX));
-                            this._thumbX = pt.clientX;
-                        }
                     }
                 }
             }
@@ -485,7 +393,6 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
             if (this.thumbIdentifier) this._hands.delete(this.thumbIdentifier);
             this._palette = undefined;
             this.thumbIdentifier = undefined;
-            this._thumbDoc = undefined;
 
             // this chunk of code is for handling the ink to text toolglass
             let scriptWorked = false;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 1a9d15c1a..dbe8fb608 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -30,6 +30,7 @@ import { ColorScheme, SettingsManager } from '../util/SettingsManager';
 import { SharingManager } from '../util/SharingManager';
 import { SnappingManager } from '../util/SnappingManager';
 import { Transform } from '../util/Transform';
+import { UndoManager } from '../util/UndoManager';
 import { TimelineMenu } from './animationtimeline/TimelineMenu';
 import { CollectionDockingView } from './collections/CollectionDockingView';
 import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOptionsMenu';
@@ -571,7 +572,7 @@ export class MainView extends React.Component {
 
     @action
     createNewFolder = async () => {
-        const folder = Docs.Create.TreeDocument([], { title: 'Untitled folder', _stayInCollection: true, isFolder: true });
+        const folder = Docs.Create.TreeDocument([], { title: 'Untitled folder', _dragOnlyWithinContainer: true, isFolder: true });
         Doc.AddDocToList(Doc.MyFilesystem, 'data', folder);
     };
 
@@ -582,6 +583,8 @@ export class MainView extends React.Component {
     waitForDoubleClick = () => (this._exploreMode ? 'never' : undefined);
     headerBarScreenXf = () => new Transform(-this.leftScreenOffsetOfMainDocView - this.leftMenuFlyoutWidth(), -this.headerBarDocHeight(), 1);
     mainScreenToLocalXf = () => new Transform(-this.leftScreenOffsetOfMainDocView - this.leftMenuFlyoutWidth(), -this.topOfMainDocContent, 1);
+    addHeaderDoc = (doc: Doc | Doc[], annotationKey?: string) => (doc instanceof Doc ? [doc] : doc).reduce((done, doc) => Doc.AddDocToList(this.headerBarDoc, 'data', doc), true);
+    removeHeaderDoc = (doc: Doc | Doc[], annotationKey?: string) => (doc instanceof Doc ? [doc] : doc).reduce((done, doc) => Doc.RemoveDocFromList(this.headerBarDoc, 'data', doc), true);
     @computed get headerBarDocView() {
         return (
             <div className="mainView-headerBar" style={{ height: this.headerBarDocHeight() }}>
@@ -589,18 +592,19 @@ export class MainView extends React.Component {
                     key="headerBarDoc"
                     Document={this.headerBarDoc}
                     DataDoc={undefined}
-                    addDocument={undefined}
                     addDocTab={DocumentViewInternal.addDocTabFunc}
                     pinToPres={emptyFunction}
                     docViewPath={returnEmptyDoclist}
                     styleProvider={DefaultStyleProvider}
                     rootSelected={returnTrue}
-                    removeDocument={returnFalse}
+                    addDocument={this.addHeaderDoc}
+                    removeDocument={this.removeHeaderDoc}
                     fitContentsToBox={returnTrue}
                     isDocumentActive={returnTrue} // headerBar is always documentActive (ie, the docView gets pointer events)
                     isContentActive={returnTrue} // headerBar is awlays contentActive which means its items are always documentActive
                     ScreenToLocalTransform={this.headerBarScreenXf}
                     childHideResizeHandles={returnTrue}
+                    childDragAction="move"
                     dontRegisterView={true}
                     hideResizeHandles={true}
                     PanelWidth={this.headerBarDocWidth}
@@ -865,7 +869,7 @@ export class MainView extends React.Component {
         this._leftMenuFlyoutWidth = 0;
     });
 
-    remButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.RemoveDocFromList(Doc.MyDockedBtns, 'data', doc), true);
+    remButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && !doc.dragOnlyWithinContainer && Doc.RemoveDocFromList(Doc.MyDockedBtns, 'data', doc), true);
     moveButtonDoc = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => this.remButtonDoc(doc) && addDocument(doc);
     addButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.AddDocToList(Doc.MyDockedBtns, 'data', doc), true);
 
diff --git a/src/client/views/Palette.scss b/src/client/views/Palette.scss
deleted file mode 100644
index 0ec879288..000000000
--- a/src/client/views/Palette.scss
+++ /dev/null
@@ -1,30 +0,0 @@
-.palette-container {
-    .palette-thumb {
-        touch-action: pan-x;
-        position: absolute;
-        height: 70px;
-        overflow: hidden;
-
-        .palette-thumbContent {
-            transition: transform .3s;
-            width: max-content;
-            overflow: hidden;
-
-            .collectionView {
-                overflow: visible;
-
-                .collectionLinearView-outer {
-                    overflow: visible;
-                }
-            }
-        }
-
-        .palette-cover {
-            width: 50px;
-            height: 50px;
-            position: absolute;
-            bottom: 0;
-            border: 1px solid black;
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/client/views/Palette.tsx b/src/client/views/Palette.tsx
deleted file mode 100644
index 749eb08a2..000000000
--- a/src/client/views/Palette.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-import { IReactionDisposer, observable, reaction } from 'mobx';
-import { observer } from 'mobx-react';
-import * as React from 'react';
-import { Doc } from '../../fields/Doc';
-import { NumCast } from '../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, emptyPath } from '../../Utils';
-import { Transform } from '../util/Transform';
-import { DocumentView } from './nodes/DocumentView';
-import './Palette.scss';
-
-export interface PaletteProps {
-    x: number;
-    y: number;
-    thumb: number[];
-    thumbDoc: Doc;
-}
-
-@observer
-export default class Palette extends React.Component<PaletteProps> {
-    private _selectedDisposer?: IReactionDisposer;
-    @observable private _selectedIndex: number = 0;
-
-    componentDidMount = () => {
-        this._selectedDisposer = reaction(
-            () => NumCast(this.props.thumbDoc.selectedIndex),
-            i => (this._selectedIndex = i),
-            { fireImmediately: true }
-        );
-    };
-
-    componentWillUnmount = () => {
-        this._selectedDisposer?.();
-    };
-
-    render() {
-        return (
-            <div className="palette-container" style={{ transform: `translate(${this.props.x}px, ${this.props.y}px)` }}>
-                <div className="palette-thumb" style={{ transform: `translate(${this.props.thumb[0] - this.props.x}px, ${this.props.thumb[1] - this.props.y}px)` }}>
-                    <div className="palette-thumbContent" style={{ transform: `translate(-${this._selectedIndex * 50 + 10}px, 0px)` }}>
-                        <DocumentView
-                            Document={this.props.thumbDoc}
-                            DataDoc={undefined}
-                            addDocument={undefined}
-                            addDocTab={returnFalse}
-                            rootSelected={returnTrue}
-                            pinToPres={emptyFunction}
-                            removeDocument={undefined}
-                            ScreenToLocalTransform={Transform.Identity}
-                            PanelWidth={() => window.screen.width}
-                            PanelHeight={() => window.screen.height}
-                            renderDepth={0}
-                            isDocumentActive={returnTrue}
-                            isContentActive={emptyFunction}
-                            focus={emptyFunction}
-                            docViewPath={returnEmptyDoclist}
-                            styleProvider={returnEmptyString}
-                            whenChildContentsActiveChanged={emptyFunction}
-                            bringToFront={emptyFunction}
-                            childFilters={returnEmptyFilter}
-                            childFiltersByRanges={returnEmptyFilter}
-                            searchFilterDocs={returnEmptyDoclist}
-                        />
-                        <div className="palette-cover" style={{ transform: `translate(${Math.max(0, this._selectedIndex) * 50.75 + 23}px, 0px)` }}></div>
-                    </div>
-                </div>
-            </div>
-        );
-    }
-}
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 1f2e21dd5..09aac053a 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -287,7 +287,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                         pinToPres={emptyFunction}
                         bringToFront={returnFalse}
                         dontRegisterView={true}
-                        dropAction={undefined}
                     />
                 </div>
             );
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 2cca2c5a9..330ccc583 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -265,6 +265,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
         }
         case StyleProp.PointerEvents:
             const isInk = doc && StrCast(Doc.Layout(doc).layout).includes(InkingStroke.name) && !props?.LayoutTemplateString;
+            if (StrCast(doc?.pointerEvents) && !props?.LayoutTemplateString?.includes(KeyValueBox.name)) return StrCast(doc!.pointerEvents); // honor pointerEvents field (set by lock button usually) if it's not a keyValue view of the Doc
             if (docProps?.DocumentView?.().ComponentView?.overridePointerEvents?.() !== undefined) return docProps?.DocumentView?.().ComponentView?.overridePointerEvents?.();
             if (MainView.Instance._exploreMode || doc?.layout_unrendered) return isInk ? 'visiblePainted' : 'all';
             if (props?.contentPointerEvents) return StrCast(props.contentPointerEvents);
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 62f47ba71..5c2ab3f70 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -97,8 +97,8 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
         TraceMobx();
         const firstDoc = this.props.docViews[0].props.Document;
         const templateName = StrCast(firstDoc.layout_fieldKey, 'layout').replace('layout_', '');
-        const noteTypes = DocListCast(Cast(Doc.UserDoc()['template-notes'], Doc, null)?.data);
-        const addedTypes = DocListCast(Cast(Doc.UserDoc()['template-clickFuncs'], Doc, null)?.data);
+        const noteTypes = DocListCast(Cast(Doc.UserDoc()['template_notes'], Doc, null)?.data);
+        const addedTypes = DocListCast(Cast(Doc.UserDoc()['template_clickFuncs'], Doc, null)?.data);
         const templateMenu: Array<JSX.Element> = [];
         this.props.templates?.forEach((checked, template) => templateMenu.push(<TemplateToggle key={template} template={template} checked={checked} toggle={this.toggleTemplate} />));
         templateMenu.push(<OtherToggle key={'default'} name={'Default'} checked={templateName === 'layout'} toggle={this.toggleDefault} />);
@@ -122,7 +122,6 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
                     rootSelected={returnFalse}
                     onCheckedClick={this.scriptField}
                     onChildClick={this.scriptField}
-                    dropAction={undefined}
                     isAnyChildContentActive={returnFalse}
                     isContentActive={returnTrue}
                     bringToFront={emptyFunction}
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 0eb61a0b2..ea02bcd4c 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -4,7 +4,7 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, Opt } from '../../../fields/Doc';
 import { NumCast, ScriptCast, StrCast } from '../../../fields/Types';
-import { returnFalse, returnZero, StopEvent } from '../../../Utils';
+import { emptyFunction, returnFalse, returnZero, StopEvent } from '../../../Utils';
 import { DragManager } from '../../util/DragManager';
 import { DocumentView, DocumentViewProps } from '../nodes/DocumentView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
@@ -60,6 +60,8 @@ export class CollectionCarouselView extends CollectionSubView() {
                         NativeHeight={returnZero}
                         onDoubleClick={this.onContentDoubleClick}
                         onClick={this.onContentClick}
+                        isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.props.isContentActive}
+                        isContentActive={this.props.childContentsActive ?? this.props.isContentActive() === false ? returnFalse : emptyFunction}
                         hideCaptions={show_captions ? true : false}
                         renderDepth={this.props.renderDepth + 1}
                         LayoutTemplate={this.props.childLayoutTemplate}
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index 675f23970..9a3ab8074 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -10,7 +10,7 @@ import { listSpec } from '../../../fields/Schema';
 import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, returnZero, smoothScroll, Utils } from '../../../Utils';
+import { emptyFunction, returnFalse, returnZero, smoothScroll, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { DragManager, dropActionType } from '../../util/DragManager';
 import { SnappingManager } from '../../util/SnappingManager';
@@ -250,7 +250,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                 dontRegisterView={dataDoc ? true : BoolCast(this.layoutDoc.childDontRegisterViews, this.props.dontRegisterView)}
                 rootSelected={this.rootSelected}
                 layout_showTitle={this.props.childlayout_showTitle}
-                dropAction={StrCast(this.layoutDoc.childDropAction) as dropActionType}
+                dragAction={StrCast(this.layoutDoc.childDragAction) as dropActionType}
                 onClick={this.onChildClickHandler}
                 onDoubleClick={this.onChildDoubleClickHandler}
                 ScreenToLocalTransform={noteTakingDocTransform}
@@ -619,7 +619,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
     }
 
     @computed get backgroundEvents() {
-        return SnappingManager.GetIsDragging();
+        return this.props.isContentActive() === false ? 'none' : undefined;
     }
 
     observer: any;
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index 57d9bbb49..15570b195 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -52,18 +52,21 @@ export class CollectionPileView extends CollectionSubView() {
     @computed get toggleIcon() {
         return ScriptField.MakeScript('documentView.iconify()', { documentView: 'any' });
     }
+    @computed get contentEvents() {
+        const isStarburst = this.layoutEngine() === computeStarburstLayout.name;
+        return this.props.isContentActive() && isStarburst ? undefined : 'none';
+    }
 
     // returns the contents of the pileup in a CollectionFreeFormView
     @computed get contents() {
-        const isStarburst = this.layoutEngine() === computeStarburstLayout.name;
         return (
-            <div className="collectionPileView-innards" style={{ pointerEvents: isStarburst || SnappingManager.GetIsDragging() ? undefined : 'none' }}>
+            <div className="collectionPileView-innards" style={{ pointerEvents: this.contentEvents }}>
                 <CollectionFreeFormView
-                    {...this.props}
+                    {...this.props} //
                     childContentsActive={returnFalse}
                     layoutEngine={this.layoutEngine}
                     addDocument={this.addPileDoc}
-                    childCanEmbedOnDrag={true}
+                    childDragAction="move"
                     childClickScript={this.toggleIcon}
                     moveDocument={this.removePileDoc}
                 />
diff --git a/src/client/views/collections/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss
index a55b70e22..a19d8e696 100644
--- a/src/client/views/collections/CollectionStackedTimeline.scss
+++ b/src/client/views/collections/CollectionStackedTimeline.scss
@@ -104,7 +104,7 @@
 
         .collectionStackedTimeline-left-resizer,
         .collectionStackedTimeline-resizer {
-            background: $medium-gray;
+            background: $dark-gray;
             position: absolute;
             top: 0;
             height: 100%;
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 9d5cb257a..e835fa659 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -119,8 +119,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
         // onClick play scripts
         CollectionStackedTimeline.RangeScript =
             CollectionStackedTimeline.RangeScript ||
-            ScriptField.MakeFunction(`setTimeout(() => scriptContext.clickAnchor(this, clientX))`, {
-                // setTimeout is a hack to run script in its own properly named undo group (instead of being part of the generic onClick)
+            ScriptField.MakeFunction(`scriptContext.clickAnchor(this, clientX)`, {
                 self: Doc.name,
                 scriptContext: 'any',
                 clientX: 'number',
@@ -406,7 +405,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                 title: ComputedField.MakeFunction(`self["${endTag}"] ? "#" + formatToTime(self["${startTag}"]) + "-" + formatToTime(self["${endTag}"]) : "#" + formatToTime(self["${startTag}"])`) as any,
                 _label_minFontSize: 12,
                 _label_maxFontSize: 24,
-                _stayInCollection: true,
+                _dragOnlyWithinContainer: true,
                 backgroundColor: 'rgba(128, 128, 128, 0.5)',
                 layout_hideLinkButton: true,
                 onClick: FollowLinkScript(),
@@ -528,6 +527,9 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
         );
     }
 
+    @computed get timelineEvents() {
+        return this.props.isContentActive() ? 'all' : this.props.isContentActive() === false ? 'none' : undefined;
+    }
     render() {
         const overlaps: {
             anchorStartTime: number;
@@ -540,7 +542,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
         }));
         const maxLevel = overlaps.reduce((m, o) => Math.max(m, o.level), 0) + 2;
         return this.clipDuration === 0 ? null : (
-            <div ref={this.createDashEventsTarget} style={{ pointerEvents: SnappingManager.GetIsDragging() ? 'all' : undefined }}>
+            <div ref={this.createDashEventsTarget} style={{ pointerEvents: this.timelineEvents }}>
                 <div
                     className="collectionStackedTimeline-timelineContainer"
                     style={{ width: this.props.PanelWidth(), cursor: SnappingManager.GetIsDragging() ? 'grab' : '' }}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 3c0d8cbc3..8eed4f716 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -17,7 +17,7 @@ import { CollectionViewType } from '../../documents/DocumentTypes';
 import { DragManager, dropActionType } from '../../util/DragManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
-import { undoBatch } from '../../util/UndoManager';
+import { undoBatch, UndoManager } from '../../util/UndoManager';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { EditableView } from '../EditableView';
@@ -295,7 +295,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
             return this.addDocument?.(newDoc);
         }
     };
-    isContentActive = () => (this.props.isSelected() || this.props.isContentActive() ? true : this.props.isSelected() === false || this.props.isContentActive() === false ? false : undefined);
+    isContentActive = () => (this.props.isContentActive() ? true : this.props.isSelected() === false || this.props.isContentActive() === false ? false : undefined);
 
     @observable _renderCount = 5;
     isChildContentActive = () =>
@@ -341,7 +341,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 dontRegisterView={BoolCast(this.layoutDoc.childDontRegisterViews, this.props.dontRegisterView)} // used to be true if DataDoc existed, but template textboxes won't layout_autoHeight resize if dontRegisterView is set, but they need to.
                 rootSelected={this.rootSelected}
                 layout_showTitle={this.props.childlayout_showTitle}
-                dropAction={StrCast(this.layoutDoc.childDropAction) as dropActionType}
+                dragAction={(this.layoutDoc.childDragAction ?? this.props.childDragAction) as dropActionType}
                 onClick={this.onChildClickHandler}
                 onDoubleClick={this.onChildDoubleClickHandler}
                 ScreenToLocalTransform={stackedDocTransform}
@@ -402,11 +402,15 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
     // This following three functions must be from the view Mehek showed
     columnDividerDown = (e: React.PointerEvent) => {
         runInAction(() => (this._cursor = 'grabbing'));
+        const batch = UndoManager.StartBatch('stacking width');
         setupMoveUpEvents(
             this,
             e,
             this.onDividerMove,
-            action(() => (this._cursor = 'grab')),
+            action(() => {
+                this._cursor = 'grab';
+                batch.end();
+            }),
             emptyFunction
         );
     };
@@ -700,7 +704,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
     }
 
     @computed get backgroundEvents() {
-        return SnappingManager.GetIsDragging();
+        return this.props.isContentActive() === false ? 'none' : undefined;
     }
     observer: any;
     render() {
@@ -710,7 +714,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
             SetValue: this.addGroup,
             contents: '+ ADD A GROUP',
         };
-        const buttonMenu = this.rootDoc.buttonMenu;
+        const buttonMenu = this.rootDoc.layout_headerButton;
         const noviceExplainer = this.rootDoc.layout_explainer;
         return (
             <>
@@ -727,7 +731,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                         style={{
                             overflowY: this.isContentActive() ? 'auto' : 'hidden',
                             background: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor),
-                            pointerEvents: (this.props.pointerEvents?.() as any) ?? (this.backgroundEvents ? 'all' : undefined),
+                            pointerEvents: (this.props.pointerEvents?.() as any) ?? this.backgroundEvents,
                         }}
                         onScroll={action(e => (this._scroll = e.currentTarget.scrollTop))}
                         onDrop={this.onExternalDrop.bind(this)}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index d1b7f6ff6..539dde7e0 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -187,7 +187,7 @@ export function CollectionSubView<X>(moreProps?: X) {
 
         protected onInternalPreDrop(e: Event, de: DragManager.DropEvent, targetAction: dropActionType) {
             if (de.complete.docDragData) {
-                // if targetDropAction is, say 'embed', but we're just dragging within a collection, we want to ignore the targetAction.
+                // if dropAction is, say 'embed', but we're just dragging within a collection, we want to ignore the targetAction.
                 // otherwise, the targetAction should become the actual action (which can still be overridden by the userDropAction -eg, shift/ctrl keys)
                 if (targetAction && !de.complete.docDragData.draggedDocuments.some(d => d.embedContainer === this.props.Document && this.childDocs.includes(d))) {
                     de.complete.docDragData.dropAction = targetAction;
@@ -212,16 +212,16 @@ export function CollectionSubView<X>(moreProps?: X) {
                     const movedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] === d);
                     const addedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] !== d);
                     if (movedDocs.length) {
-                        const canAdd = this.props.Document._type_collection === CollectionViewType.Pile || de.embedKey || this.props.Document.allowOverlayDrop || Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.props.Document);
-                        added = docDragData.moveDocument(movedDocs, this.props.Document, canAdd ? this.addDocument : returnFalse);
+                        const canAdd = de.embedKey || dropAction || Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.rootDoc);
+                        added = docDragData.moveDocument(movedDocs, this.rootDoc, canAdd ? this.addDocument : returnFalse);
                     } else {
-                        ScriptCast(this.props.Document.dropConverter)?.script.run({ dragData: docDragData });
+                        ScriptCast(this.rootDoc.dropConverter)?.script.run({ dragData: docDragData });
                         added = addedDocs.length ? this.addDocument(addedDocs) : true;
                     }
                     added && e.stopPropagation();
                     return added;
                 } else {
-                    ScriptCast(this.props.Document.dropConverter)?.script.run({ dragData: docDragData });
+                    ScriptCast(this.rootDoc.dropConverter)?.script.run({ dragData: docDragData });
                     added = this.addDocument(docDragData.droppedDocuments);
                 }
                 !added && alert('You cannot perform this move');
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 60e6815e5..192d48c64 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -34,8 +34,6 @@ export class CollectionTimeView extends CollectionSubView() {
 
     async componentDidMount() {
         this.props.setContentView?.(this);
-        //const detailView = (await DocCastAsync(this.props.Document.childClickedOpenTemplateView)) || DocUtils.findTemplate("detailView", StrCast(this.rootDoc.type), "");
-        ///const childText = "const embedding = getEmbedding(self); switchView(embedding, detailView); embedding.dropAction='embed'; useRightSplit(embedding, shiftKey); ";
         runInAction(() => {
             this._childClickedScript = ScriptField.MakeScript('openInLightbox(self)', { this: Doc.name });
             this._viewDefDivClick = ScriptField.MakeScript('pivotColumnClick(this,payload)', { payload: 'any' });
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index ed1e0c067..7598bb753 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -3,7 +3,6 @@ import { observer } from 'mobx-react';
 import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc';
 import { DocData, Height, Width } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
-import { InkTool } from '../../../fields/InkField';
 import { listSpec } from '../../../fields/Schema';
 import { ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
@@ -90,8 +89,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     // these should stay in synch with counterparts in DocComponent.ts ViewBoxAnnotatableComponent
     @observable _isAnyChildContentActive = false;
     whenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive)));
-    isContentActive = (outsideReaction?: boolean) =>
-        Doc.ActiveTool !== InkTool.None || this.props.isContentActive?.() || this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this._isAnyChildContentActive || this.props.rootSelected(outsideReaction) ? true : false;
+    isContentActive = (outsideReaction?: boolean) => (this._isAnyChildContentActive ? true : this.props.isContentActive() ? true : false);
 
     componentWillUnmount() {
         this._isDisposing = true;
@@ -261,7 +259,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     @observable _renderCount = 1;
     @computed get treeViewElements() {
         TraceMobx();
-        const dropAction = StrCast(this.doc.childDropAction) as dropActionType;
+        const dragAction = StrCast(this.doc.childDragAction) as dropActionType;
         const addDoc = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => this.addDoc(doc, relativeTo, before);
         const moveDoc = (d: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this.props.moveDocument?.(d, target, addDoc) || false;
         if (this._renderCount < this.treeChildren.length) setTimeout(action(() => (this._renderCount = Math.min(this.treeChildren.length, this._renderCount + 20))));
@@ -276,7 +274,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
             addDoc,
             this.remove,
             moveDoc,
-            dropAction,
+            dragAction,
             this.props.addDocTab,
             this.props.styleProvider,
             this.screenToLocalTransform,
@@ -383,7 +381,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     @observable _headerHeight = 0;
     @computed get content() {
         const background = () => this.props.styleProvider?.(this.doc, this.props, StyleProp.BackgroundColor);
-        const pointerEvents = () => (!this.props.isContentActive() && !SnappingManager.GetIsDragging() ? 'none' : undefined);
+        const pointerEvents = () => (this.props.isContentActive() === false ? 'none' : undefined);
         const titleBar = this.props.treeViewHideTitle || this.doc.treeViewHideTitle ? null : this.titleBar;
         return (
             <div style={{ display: 'flex', flexDirection: 'column', height: '100%', pointerEvents: 'all' }}>
@@ -440,12 +438,12 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
                         setContentView={emptyFunction}
                         NativeWidth={returnZero}
                         NativeHeight={returnZero}
-                        pointerEvents={SnappingManager.GetIsDragging() ? returnAll : returnNone}
+                        pointerEvents={this.props.isContentActive() && SnappingManager.GetIsDragging() ? returnAll : returnNone}
                         isAnnotationOverlay={true}
                         isAnnotationOverlayScrollable={true}
                         childDocumentsActive={this.props.isDocumentActive}
                         fieldKey={this.props.fieldKey + '_annotations'}
-                        dropAction={'move'}
+                        dropAction="move"
                         select={emptyFunction}
                         addDocument={this.addAnnotationDocument}
                         removeDocument={this.remAnnotationDocument}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 7913d3188..c33519afb 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -9,6 +9,7 @@ import { TraceMobx } from '../../../fields/util';
 import { returnEmptyString } from '../../../Utils';
 import { DocUtils } from '../../documents/Documents';
 import { CollectionViewType } from '../../documents/DocumentTypes';
+import { dropActionType } from '../../util/DragManager';
 import { ImageUtils } from '../../util/Import & Export/ImageUtils';
 import { InteractionUtils } from '../../util/InteractionUtils';
 import { ContextMenu } from '../ContextMenu';
@@ -55,7 +56,7 @@ interface CollectionViewProps_ extends FieldViewProps {
     childHideDecorationTitle?: () => boolean;
     childHideResizeHandles?: () => boolean;
     childLayoutTemplate?: () => Doc | undefined; // specify a layout Doc template to use for children of the collection
-    childCanEmbedOnDrag?: boolean;
+    childDragAction?: dropActionType;
     childXPadding?: number;
     childYPadding?: number;
     childLayoutString?: string;
@@ -220,7 +221,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
     childHideResizeHandles = () => this.props.childHideResizeHandles?.() ?? BoolCast(this.Document.childHideResizeHandles);
     childHideDecorationTitle = () => this.props.childHideDecorationTitle?.() ?? BoolCast(this.Document.childHideDecorationTitle);
     childLayoutTemplate = () => this.props.childLayoutTemplate?.() || Cast(this.rootDoc.childLayoutTemplate, Doc, null);
-    isContentActive = (outsideReaction?: boolean) => this.props.isContentActive() || this.isAnyChildContentActive();
+    isContentActive = (outsideReaction?: boolean) => (this.isAnyChildContentActive() ? true : this.props.isContentActive());
 
     render() {
         TraceMobx();
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 3b99339af..97d4d989b 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -583,7 +583,6 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
                     isContentActive={emptyFunction}
                     isAnyChildContentActive={returnFalse}
                     select={emptyFunction}
-                    dropAction={undefined}
                     isSelected={returnFalse}
                     dontRegisterView={true}
                     fieldKey={Doc.LayoutFieldKey(this.props.document)}
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 6bc30c451..91ae2b3cc 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -21,7 +21,7 @@ import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { SelectionManager } from '../../util/SelectionManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
-import { undoBatch, UndoManager } from '../../util/UndoManager';
+import { undoable, undoBatch, UndoManager } from '../../util/UndoManager';
 import { EditableView } from '../EditableView';
 import { TREE_BULLET_WIDTH } from '../global/globalCssVariables.scss';
 import { DocumentView, DocumentViewInternal, DocumentViewProps, OpenWhere, StyleProviderFunc } from '../nodes/DocumentView';
@@ -45,7 +45,7 @@ export interface TreeViewProps {
     dataDoc?: Doc;
     treeViewParent: Doc;
     renderDepth: number;
-    dropAction: dropActionType;
+    dragAction: dropActionType;
     addDocTab: (doc: Doc, where: OpenWhere) => boolean;
     panelWidth: () => number;
     panelHeight: () => number;
@@ -276,7 +276,7 @@ export class TreeView extends React.Component<TreeViewProps> {
     };
     onPointerEnter = (e: React.PointerEvent): void => {
         this.props.isContentActive(true) && Doc.BrushDoc(this.dataDoc);
-        if (e.buttons === 1 && SnappingManager.GetIsDragging()) {
+        if (e.buttons === 1 && SnappingManager.GetIsDragging() && this.props.isContentActive()) {
             this._header.current!.className = 'treeView-header';
             document.removeEventListener('pointermove', this.onDragMove, true);
             document.removeEventListener('pointerup', this.onDragUp, true);
@@ -341,7 +341,7 @@ export class TreeView extends React.Component<TreeViewProps> {
     };
 
     makeFolder = () => {
-        const folder = Docs.Create.TreeDocument([], { title: 'Untitled folder', _stayInCollection: true, isFolder: true });
+        const folder = Docs.Create.TreeDocument([], { title: 'Untitled folder', _dragOnlyWithinContainer: true, isFolder: true });
         TreeView._editTitleOnLoad = { id: folder[Id], parent: this.props.parentTreeView };
         return this.props.addDocument(folder);
     };
@@ -391,7 +391,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         const canAdd = (!this.props.treeView.outlineMode && !StrCast((inside ? this.props.document : this.props.treeViewParent)?.treeViewFreezeChildren).includes('add')) || forceAdd;
         if (canAdd) {
             this.props.parentTreeView instanceof TreeView && (this.props.parentTreeView.dropping = true);
-            const res = UndoManager.RunInTempBatch(() => droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (dropAction === 'proto' ? addDoc(d) : false) : addDoc(d)) || added, false));
+            const res = droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (dropAction === 'proto' ? addDoc(d) : false) : addDoc(d)) || added, false);
             this.props.parentTreeView instanceof TreeView && (this.props.parentTreeView.dropping = false);
             return res;
         }
@@ -460,7 +460,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                     addDoc,
                     remDoc,
                     moveDoc,
-                    this.props.dropAction,
+                    this.props.dragAction,
                     this.props.addDocTab,
                     this.titleStyleProvider,
                     this.props.ScreenToLocalTransform,
@@ -585,12 +585,12 @@ export class TreeView extends React.Component<TreeViewProps> {
                             downY = e.clientY;
                             e.stopPropagation();
                         }}
-                        onClick={e => {
+                        onClick={undoable(e => {
                             if (this.props.isContentActive() && Math.abs(e.clientX - downX) < 3 && Math.abs(e.clientY - downY) < 3) {
                                 !this.props.treeView.outlineMode && (this.doc.treeViewSortCriterion = sortKeys[(curSortIndex + 1) % sortKeys.length]);
                                 e.stopPropagation();
                             }
-                        }}>
+                        }, 'sort order')}>
                         {!docs
                             ? null
                             : TreeView.GetChildElements(
@@ -604,7 +604,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                                   addDoc,
                                   remDoc,
                                   moveDoc,
-                                  StrCast(this.doc.childDropAction, this.props.dropAction) as dropActionType,
+                                  StrCast(this.doc.childDragAction, this.props.dragAction) as dropActionType,
                                   this.props.addDocTab,
                                   this.titleStyleProvider,
                                   this.props.ScreenToLocalTransform,
@@ -904,7 +904,6 @@ export class TreeView extends React.Component<TreeViewProps> {
                 hideDecorationTitle={this.props.treeView.outlineMode}
                 hideResizeHandles={this.props.treeView.outlineMode}
                 styleProvider={this.titleStyleProvider}
-                enableDragWhenActive={true}
                 onClickScriptDisable="never" // tree docViews have a script to show fields, etc.
                 docViewPath={this.props.treeView.props.docViewPath}
                 treeViewDoc={this.props.treeView.props.Document}
@@ -914,7 +913,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                 pinToPres={emptyFunction}
                 onClick={this.onChildClick}
                 onDoubleClick={this.onChildDoubleClick}
-                dropAction={this.props.dropAction}
+                dragAction={this.props.dragAction}
                 moveDocument={this.move}
                 removeDocument={this.props.removeDoc}
                 ScreenToLocalTransform={this.getTransform}
@@ -950,7 +949,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                         fontWeight: Doc.IsSearchMatch(this.doc) !== undefined ? 'bold' : undefined,
                         textDecoration: Doc.GetT(this.doc, 'title', 'string', true) ? 'underline' : undefined,
                         outline: this.doc === Doc.ActiveDashboard ? 'dashed 1px #06123232' : undefined,
-                        pointerEvents: !this.props.isContentActive() && !SnappingManager.GetIsDragging() ? 'none' : undefined,
+                        pointerEvents: !this.props.isContentActive() ? 'none' : undefined,
                     }}>
                     {view}
                 </div>
@@ -1132,7 +1131,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         add: (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => boolean,
         remove: undefined | ((doc: Doc | Doc[]) => boolean),
         move: DragManager.MoveFunction,
-        dropAction: dropActionType,
+        dragAction: dropActionType,
         addDocTab: (doc: Doc, where: OpenWhere) => boolean,
         styleProvider: undefined | StyleProviderFunc,
         screenToLocalXf: () => Transform,
@@ -1218,7 +1217,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                         panelHeight={rowHeight}
                         dontRegisterView={dontRegisterView}
                         moveDocument={move}
-                        dropAction={dropAction}
+                        dragAction={dragAction}
                         addDocTab={addDocTab}
                         ScreenToLocalTransform={screenToLocalXf}
                         isContentActive={isContentActive}
@@ -1239,6 +1238,6 @@ export class TreeView extends React.Component<TreeViewProps> {
 
 ScriptingGlobals.add(function TreeView_addNewFolder() {
     TreeView._editTitleOnLoad = { id: Utils.GenerateGuid(), parent: undefined };
-    const opts = { title: 'Untitled folder', _stayInCollection: true, isFolder: true };
+    const opts = { title: 'Untitled folder', _dragOnlyWithinContainer: true, isFolder: true };
     return Doc.AddDocToList(Doc.MyFilesystem, 'data', Docs.Create.TreeDocument([], opts, TreeView._editTitleOnLoad.id));
 });
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 36472dd2e..d7e073c5f 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1240,7 +1240,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         };
     };
 
-    isContentActive = () => this.props.isSelected() || this.props.isContentActive();
+    isContentActive = () => this.props.isContentActive();
 
     @undoBatch
     @action
@@ -1267,7 +1267,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
         const pointerEvents = DocumentDecorations.Instance.Interacting
             ? 'none'
-            : this.props.childPointerEvents ?? (this.props.viewDefDivClick || (engine === computePassLayout.name && !this.props.isSelected(true)) ? 'none' : this.props.pointerEvents?.());
+            : this.props.childPointerEvents ?? (this.props.viewDefDivClick || (engine === computePassLayout.name && !this.props.isSelected(true)) || this.isContentActive() === false ? 'none' : this.props.pointerEvents?.());
         return pointerEvents;
     };
     getChildDocView(entry: PoolData) {
@@ -1298,7 +1298,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 childFiltersByRanges={this.childDocRangeFilters}
                 searchFilterDocs={this.searchFilterDocs}
                 isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive}
-                isContentActive={this.props.childContentsActive ?? emptyFunction}
+                isContentActive={this.props.childContentsActive ?? this.props.isContentActive() === false ? returnFalse : emptyFunction}
                 focus={this.Document._isGroup ? this.groupFocus : this.isAnnotationOverlay ? this.props.focus : this.focus}
                 addDocTab={this.addDocTab}
                 addDocument={this.props.addDocument}
@@ -1308,10 +1308,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
                 docViewPath={this.props.docViewPath}
                 styleProvider={this.getClusterColor}
-                canEmbedOnDrag={this.props.childCanEmbedOnDrag}
+                dragAction={(this.rootDoc.childDragAction ?? this.props.childDragAction) as dropActionType}
                 dataProvider={this.childDataProvider}
                 sizeProvider={this.childSizeProvider}
-                dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
                 bringToFront={this.bringToFront}
                 layout_showTitle={this.props.childlayout_showTitle}
                 dontRegisterView={this.props.dontRenderDocuments || this.props.dontRegisterView}
@@ -1987,7 +1986,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 onDragOver={e => e.preventDefault()}
                 onContextMenu={this.onContextMenu}
                 style={{
-                    pointerEvents: SnappingManager.GetIsDragging() && this.childDocs.includes(DragManager.docsBeingDragged.lastElement()) ? 'all' : (this.props.pointerEvents?.() as any),
+                    pointerEvents: this.props.isContentActive() && SnappingManager.GetIsDragging() ? 'all' : (this.props.pointerEvents?.() as any),
                     textAlign: this.isAnnotationOverlay ? 'initial' : undefined,
                     transform: `scale(${this.nativeDimScaling || 1})`,
                     width: `${100 / (this.nativeDimScaling || 1)}%`,
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 77cb7f88c..898452ccb 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -386,7 +386,6 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         newCollection._height = this.Bounds.height;
         newCollection._isGroup = makeGroup;
         newCollection.forceActive = makeGroup;
-        newCollection.enableDragWhenActive = makeGroup;
         newCollection.x = this.Bounds.left;
         newCollection.y = this.Bounds.top;
         newCollection.layout_fitWidth = true;
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index e8ae88ae5..cd8b7a0cc 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -368,7 +368,7 @@ export class CollectionGridView extends CollectionSubView() {
             <div
                 className="collectionGridView-contents"
                 ref={this.createDashEventsTarget}
-                style={{ pointerEvents: !this.props.isContentActive() && !SnappingManager.GetIsDragging() ? 'none' : undefined }}
+                style={{ pointerEvents: !this.props.isContentActive() ? 'none' : undefined }}
                 onContextMenu={this.onContextMenu}
                 onPointerDown={this.onPointerDown}
                 onDrop={this.onExternalDrop}>
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index 65578f214..8f90e4444 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -1,6 +1,6 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@material-ui/core';
-import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, Opt } from '../../../../fields/Doc';
@@ -50,34 +50,10 @@ export class CollectionLinearView extends CollectionSubView() {
             width => this.childDocs.length && (this.layoutDoc._width = width),
             { fireImmediately: true }
         );
-
-        this._selectedDisposer = reaction(
-            () => NumCast(this.layoutDoc.selectedIndex),
-            i =>
-                runInAction(() => {
-                    this._selectedIndex = i;
-                    let selected: any = undefined;
-                    this.childLayoutPairs.map(async (pair, ind) => {
-                        const isSelected = this._selectedIndex === ind;
-                        if (isSelected) {
-                            selected = pair;
-                        } else {
-                            ScriptCast(DocCast(pair.layout.proto)?.onPointerUp)?.script.run({ this: pair.layout.proto }, console.log);
-                        }
-                    });
-                    if (selected && selected.layout) {
-                        ScriptCast(DocCast(selected.layout.proto)?.onPointerDown)?.script.run({ this: selected.layout.proto }, console.log);
-                    }
-                }),
-            { fireImmediately: true }
-        );
     }
     protected createDashEventsTarget = (ele: HTMLDivElement | null) => {
-        //used for stacking and masonry view
-        this._dropDisposer && this._dropDisposer();
-        if (ele) {
-            this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
-        }
+        this._dropDisposer?.();
+        if (ele) this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
     };
 
     dimension = () => NumCast(this.rootDoc._height);
@@ -89,12 +65,8 @@ export class CollectionLinearView extends CollectionSubView() {
 
     @action
     exitLongLinks = () => {
-        if (DocumentLinksButton.StartLink) {
-            if (DocumentLinksButton.StartLink.Document) {
-                action((e: React.PointerEvent<HTMLDivElement>) => {
-                    Doc.UnBrushDoc(DocumentLinksButton.StartLink?.Document as Doc);
-                });
-            }
+        if (DocumentLinksButton.StartLink?.Document) {
+            action((e: React.PointerEvent<HTMLDivElement>) => Doc.UnBrushDoc(DocumentLinksButton.StartLink?.Document as Doc));
         }
         DocumentLinksButton.StartLink = undefined;
         DocumentLinksButton.StartLinkView = undefined;
@@ -201,7 +173,7 @@ export class CollectionLinearView extends CollectionSubView() {
                     moveDocument={this.props.moveDocument}
                     addDocTab={this.props.addDocTab}
                     pinToPres={emptyFunction}
-                    dropAction={StrCast(this.layoutDoc.childDropAction) as dropActionType}
+                    dragAction={(this.layoutDoc.childDragAction ?? this.props.childDragAction) as dropActionType}
                     rootSelected={this.props.isSelected}
                     removeDocument={this.props.removeDocument}
                     ScreenToLocalTransform={docXf}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 34fa0343d..32b42223b 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -5,7 +5,6 @@ import { Doc, DocListCast } from '../../../../fields/Doc';
 import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { returnFalse } from '../../../../Utils';
 import { DragManager, dropActionType } from '../../../util/DragManager';
-import { SnappingManager } from '../../../util/SnappingManager';
 import { Transform } from '../../../util/Transform';
 import { undoBatch } from '../../../util/UndoManager';
 import { DocumentView } from '../../nodes/DocumentView';
@@ -194,11 +193,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
         let dropInd = -1;
         if (de.complete.docDragData && this._mainCont) {
             let curInd = -1;
-            de.complete.docDragData?.droppedDocuments.forEach(
-                action((d: Doc) => {
-                    curInd = this.childDocs.indexOf(d);
-                })
-            );
+            de.complete.docDragData?.droppedDocuments.forEach(d => (curInd = this.childDocs.indexOf(d)));
             Array.from(this._mainCont.children).forEach((child, index) => {
                 const brect = child.getBoundingClientRect();
                 if (brect.x < de.x && brect.x + brect.width > de.x) {
@@ -234,7 +229,14 @@ export class CollectionMulticolumnView extends CollectionSubView() {
     onChildDoubleClickHandler = () => ScriptCast(this.Document.onChildDoubleClick);
 
     isContentActive = () => this.props.isSelected() || this.props.isContentActive() || this.props.isAnyChildContentActive();
-    isChildContentActive = () => (((this.props.childDocumentsActive?.() || this.Document._childDocumentsActive) && this.props.isDocumentActive?.() && SnappingManager.GetIsDragging()) || this.isContentActive() ? true : false);
+    isChildContentActive = () => {
+        const childDocsActive = this.props.childDocumentsActive?.() ?? this.rootDoc.childDocumentsActive;
+        return this.props.isContentActive?.() === false || childDocsActive === false
+            ? false //
+            : this.props.isDocumentActive?.() && childDocsActive
+            ? true
+            : undefined;
+    };
     getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number) => {
         return (
             <DocumentView
@@ -248,7 +250,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
                 PanelWidth={width}
                 PanelHeight={height}
                 rootSelected={this.rootSelected}
-                dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
+                dragAction={(this.props.Document.childDragAction ?? this.props.childDragAction) as dropActionType}
                 onClick={this.onChildClickHandler}
                 onDoubleClick={this.onChildDoubleClickHandler}
                 suppressSetHeight={true}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index bab77ed48..fa3a9f352 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -5,7 +5,6 @@ import { Doc, DocListCast } from '../../../../fields/Doc';
 import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { returnFalse } from '../../../../Utils';
 import { DragManager, dropActionType } from '../../../util/DragManager';
-import { SnappingManager } from '../../../util/SnappingManager';
 import { Transform } from '../../../util/Transform';
 import { undoBatch } from '../../../util/UndoManager';
 import { DocumentView } from '../../nodes/DocumentView';
@@ -194,11 +193,7 @@ export class CollectionMultirowView extends CollectionSubView() {
         let dropInd = -1;
         if (de.complete.docDragData && this._mainCont) {
             let curInd = -1;
-            de.complete.docDragData?.droppedDocuments.forEach(
-                action((d: Doc) => {
-                    curInd = this.childDocs.indexOf(d);
-                })
-            );
+            de.complete.docDragData?.droppedDocuments.forEach(d => (curInd = this.childDocs.indexOf(d)));
             Array.from(this._mainCont.children).forEach((child, index) => {
                 const brect = child.getBoundingClientRect();
                 if (brect.y < de.y && brect.y + brect.height > de.y) {
@@ -234,7 +229,14 @@ export class CollectionMultirowView extends CollectionSubView() {
     onChildDoubleClickHandler = () => ScriptCast(this.Document.onChildDoubleClick);
 
     isContentActive = () => this.props.isSelected() || this.props.isContentActive() || this.props.isAnyChildContentActive();
-    isChildContentActive = () => (((this.props.childDocumentsActive?.() || this.Document._childDocumentsActive) && this.props.isDocumentActive?.() && SnappingManager.GetIsDragging()) || this.isContentActive() ? true : false);
+    isChildContentActive = () => {
+        const childDocsActive = this.props.childDocumentsActive?.() ?? this.rootDoc.childDocumentsActive;
+        return this.props.isContentActive?.() === false || childDocsActive === false
+            ? false //
+            : this.props.isDocumentActive?.() && childDocsActive
+            ? true
+            : undefined;
+    };
     getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number) => {
         return (
             <DocumentView
@@ -248,7 +250,7 @@ export class CollectionMultirowView extends CollectionSubView() {
                 PanelWidth={width}
                 PanelHeight={height}
                 rootSelected={this.rootSelected}
-                dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
+                dropAction={StrCast(this.rootDoc.childDragAction) as dropActionType}
                 onClick={this.onChildClickHandler}
                 onDoubleClick={this.onChildDoubleClickHandler}
                 ScreenToLocalTransform={dxf}
@@ -257,6 +259,7 @@ export class CollectionMultirowView extends CollectionSubView() {
                 hideResizeHandles={this.props.childHideResizeHandles?.()}
                 hideDecorationTitle={this.props.childHideDecorationTitle?.()}
                 fitContentsToBox={this.props.fitContentsToBox}
+                dragAction={this.props.childDragAction}
                 focus={this.props.focus}
                 childFilters={this.childDocFilters}
                 childFiltersByRanges={this.childDocRangeFilters}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 15424de98..06b227b4b 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -952,7 +952,7 @@ class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsP
                                 styleProvider={DefaultStyleProvider}
                                 waitForDoubleClickToClick={returnNever}
                                 defaultDoubleClick={returnIgnore}
-                                enableDragWhenActive={true}
+                                dragAction="move"
                                 onClickScriptDisable="always"
                                 focus={this.props.schema.focusDocument}
                                 childFilters={this.props.schema.childDocFilters}
@@ -970,7 +970,6 @@ class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsP
                                 hideLinkAnchors={true}
                                 layout_fitWidth={returnTrue}
                                 scriptContext={this}
-                                canEmbedOnDrag={true}
                             />
                         </div>
                     );
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index 4f3503751..e8e606030 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -53,13 +53,13 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() {
     };
 
     onPointerEnter = (e: any) => {
-        if (!SnappingManager.GetIsDragging()) return;
-        document.removeEventListener('pointermove', this.onPointerMove);
-        document.addEventListener('pointermove', this.onPointerMove);
+        if (SnappingManager.GetIsDragging() && this.props.isContentActive()) {
+            document.removeEventListener('pointermove', this.onPointerMove);
+            document.addEventListener('pointermove', this.onPointerMove);
+        }
     };
 
     onPointerMove = (e: any) => {
-        if (!SnappingManager.GetIsDragging()) return;
         const dragIsRow = DragManager.docsBeingDragged.some(doc => doc.embedContainer === this.schemaDoc); //  this.schemaView?._selectedDocs.has(doc) ?? false;
 
         if (this._ref && dragIsRow) {
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 97264508c..a958f53ea 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -76,7 +76,7 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
             isSelected: returnFalse,
             setHeight: returnFalse,
             select: emptyFunction,
-            dropAction: 'embed',
+            dragAction: 'move',
             bringToFront: emptyFunction,
             renderDepth: 1,
             isContentActive: returnFalse,
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 5af05e491..9ceac3b8c 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -78,7 +78,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
             e,
             e => {
                 const dragData = new DragManager.DocumentDragData([this.props.linkDoc], 'embed');
-                dragData.removeDropProperties = ['hidden'];
+                dragData.dropPropertiesToRemove = ['hidden'];
                 DragManager.StartDocumentDrag([this._editRef.current!], dragData, e.x, e.y);
                 return true;
             },
diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx
index 664a0fa4f..745efea08 100644
--- a/src/client/views/linking/LinkPopup.tsx
+++ b/src/client/views/linking/LinkPopup.tsx
@@ -70,7 +70,6 @@ export class LinkPopup extends React.Component<LinkPopupProps> {
                         linkSearch={true}
                         linkCreated={this.props.linkCreated}
                         fieldKey="data"
-                        dropAction="move"
                         isSelected={returnTrue}
                         isContentActive={returnTrue}
                         select={returnTrue}
diff --git a/src/client/views/nodes/ComparisonBox.scss b/src/client/views/nodes/ComparisonBox.scss
index 660045a6f..a12f1c12b 100644
--- a/src/client/views/nodes/ComparisonBox.scss
+++ b/src/client/views/nodes/ComparisonBox.scss
@@ -93,4 +93,4 @@
             display: flex;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index e6a4635c0..d747c4527 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -2,11 +2,10 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, Opt } from '../../../fields/Doc';
-import { Cast, NumCast, StrCast } from '../../../fields/Types';
+import { DocCast, NumCast, StrCast } from '../../../fields/Types';
 import { emptyFunction, returnFalse, returnNone, returnZero, setupMoveUpEvents } from '../../../Utils';
-import { Docs } from '../../documents/Documents';
+import { Docs, DocUtils } from '../../documents/Documents';
 import { DragManager } from '../../util/DragManager';
-import { SnappingManager } from '../../util/SnappingManager';
 import { undoBatch } from '../../util/UndoManager';
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
 import { StyleProp } from '../StyleProvider';
@@ -32,15 +31,12 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
     get clipWidthKey() {
         return '_' + this.props.fieldKey + '_clipWidth';
     }
-
     componentDidMount() {
         this.props.setContentView?.(this);
     }
-
     protected createDropTarget = (ele: HTMLDivElement | null, fieldKey: string, disposerId: number) => {
         this._disposers[disposerId]?.();
         if (ele) {
-            // create disposers identified by disposerId to remove drag & drop listeners
             this._disposers[disposerId] = DragManager.MakeDropTarget(ele, (e, dropEvent) => this.internalDrop(e, dropEvent, fieldKey), this.layoutDoc);
         }
     };
@@ -50,29 +46,40 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
         if (dropEvent.complete.docDragData) {
             event.stopPropagation(); // prevent parent Doc from registering new position so that it snaps back into place
             const droppedDocs = dropEvent.complete.docDragData?.droppedDocuments;
-            droppedDocs.lastElement().embedContainer = this.dataDoc;
-            this.dataDoc[fieldKey] = droppedDocs.lastElement();
+            const res = dropEvent.complete.docDragData.moveDocument?.(droppedDocs, this.rootDoc, (doc: Doc | Doc[]) => this.addDoc(doc instanceof Doc ? doc : doc.lastElement(), fieldKey));
+            res && (droppedDocs.lastElement().embedContainer = this.dataDoc);
+            return res;
         }
     };
 
     private registerSliding = (e: React.PointerEvent<HTMLDivElement>, targetWidth: number) => {
-        e.button !== 2 &&
+        if (e.button !== 2) {
             setupMoveUpEvents(
                 this,
                 e,
                 this.onPointerMove,
                 emptyFunction,
+                action((e, doubleTap) => {
+                    if (doubleTap) {
+                        this._isAnyChildContentActive = true;
+                        if (!this.dataDoc[this.fieldKey + '_1']) this.dataDoc[this.fieldKey + '_1'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc);
+                        if (!this.dataDoc[this.fieldKey + '_2']) this.dataDoc[this.fieldKey + '_2'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc);
+                    }
+                }),
+                false,
+                undefined,
                 action(() => {
-                    // on click, animate slider movement to the targetWidth
+                    if (this._isAnyChildContentActive) return;
                     this._animating = 'all 200ms';
+                    // on click, animate slider movement to the targetWidth
                     this.layoutDoc[this.clipWidthKey] = (targetWidth * 100) / this.props.PanelWidth();
                     setTimeout(
                         action(() => (this._animating = '')),
                         200
                     );
-                }),
-                false
+                })
             );
+        }
     };
 
     @action
@@ -86,7 +93,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
 
     getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
         const anchor = Docs.Create.ConfigDocument({
-            title: 'ImgAnchor:' + this.rootDoc.title,
+            title: 'CompareAnchor:' + this.rootDoc.title,
             // set presentation timing properties for restoring view
             presTransition: 1000,
             annotationOn: this.rootDoc,
@@ -105,12 +112,29 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
         e.stopPropagation; // prevent click event action (slider movement) in registerSliding
         delete this.dataDoc[fieldKey];
     };
+    moveDoc = (doc: Doc, addDocument: (document: Doc | Doc[]) => boolean, which: string) => this.remDoc(doc, which) && addDocument(doc);
+    addDoc = (doc: Doc, which: string) => {
+        this.dataDoc[which] = doc;
+        return true;
+    };
+    remDoc = (doc: Doc, which: string) => {
+        if (this.dataDoc[which] === doc) {
+            this.dataDoc[which] = undefined;
+            return true;
+        }
+        return false;
+    };
+
+    whenChildContentsActiveChanged = action((isActive: boolean) => (this._isAnyChildContentActive = isActive));
 
     docStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
         if (property === StyleProp.PointerEvents) return 'none';
         return this.props.styleProvider?.(doc, props, property);
     };
-
+    moveDoc1 = (doc: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_1'), true);
+    moveDoc2 = (doc: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_2'), true);
+    remDoc1 = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_1'), true);
+    remDoc2 = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_2'), true);
     render() {
         const clearButton = (which: string) => {
             return (
@@ -118,33 +142,35 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                     className={`clear-button ${which}`}
                     onPointerDown={e => e.stopPropagation()} // prevent triggering slider movement in registerSliding
                     onClick={e => this.clearDoc(e, which)}>
-                    <FontAwesomeIcon className={`clear-button ${which}`} icon={'times'} size="sm" />
+                    <FontAwesomeIcon className={`clear-button ${which}`} icon="times" size="sm" />
                 </div>
             );
         };
         const displayDoc = (which: string) => {
-            const whichDoc = Cast(this.dataDoc[which], Doc, null);
-            // if (whichDoc?.type === DocumentType.MARKER) whichDoc = Cast(whichDoc.annotationOn, Doc, null);
-            const targetDoc = Cast(whichDoc?.annotationOn, Doc, null) ?? whichDoc;
-            return whichDoc ? (
+            const whichDoc = DocCast(this.dataDoc[which]);
+            const targetDoc = DocCast(whichDoc?.annotationOn, whichDoc);
+            return targetDoc ? (
                 <>
                     <DocumentView
                         {...this.props}
+                        Document={targetDoc}
+                        DataDoc={undefined}
+                        moveDocument={which.endsWith('1') ? this.moveDoc1 : this.moveDoc2}
+                        removeDocument={which.endsWith('1') ? this.remDoc1 : this.remDoc2}
                         NativeWidth={returnZero}
                         NativeHeight={returnZero}
-                        isContentActive={returnFalse}
+                        isContentActive={emptyFunction}
                         isDocumentActive={returnFalse}
-                        styleProvider={this.docStyleProvider}
-                        Document={targetDoc}
-                        DataDoc={undefined}
+                        whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
+                        styleProvider={this._isAnyChildContentActive ? this.props.styleProvider : this.docStyleProvider}
                         hideLinkButton={true}
-                        pointerEvents={returnNone}
+                        pointerEvents={this._isAnyChildContentActive ? undefined : returnNone}
                     />
                     {clearButton(which)}
                 </> // placeholder image if doc is missing
             ) : (
                 <div className="placeholder">
-                    <FontAwesomeIcon className="upload-icon" icon={'cloud-upload-alt'} size="lg" />
+                    <FontAwesomeIcon className="upload-icon" icon="cloud-upload-alt" size="lg" />
                 </div>
             );
         };
@@ -157,7 +183,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
         };
 
         return (
-            <div className={`comparisonBox${this.props.isContentActive() || SnappingManager.GetIsDragging() ? '-interactive' : ''}` /* change className to easily disable/enable pointer events in CSS */}>
+            <div className={`comparisonBox${this.props.isContentActive() ? '-interactive' : ''}` /* change className to easily disable/enable pointer events in CSS */}>
                 {displayBox(`${this.fieldKey}_2`, 1, this.props.PanelWidth() - 3)}
                 <div className="clip-div" style={{ width: this.clipWidth + '%', transition: this._animating, background: StrCast(this.layoutDoc._backgroundColor, 'gray') }}>
                     {displayBox(`${this.fieldKey}_1`, 0, 0)}
@@ -169,7 +195,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                         left: `calc(${this.clipWidth + '%'} - 0.5px)`,
                         cursor: this.clipWidth < 5 ? 'e-resize' : this.clipWidth / 100 > (this.props.PanelWidth() - 5) / this.props.PanelWidth() ? 'w-resize' : undefined,
                     }}
-                    onPointerDown={e => this.registerSliding(e, this.props.PanelWidth() / 2)} /* if clicked, return slide-bar to center */
+                    onPointerDown={e => !this._isAnyChildContentActive && this.registerSliding(e, this.props.PanelWidth() / 2)} /* if clicked, return slide-bar to center */
                 >
                     <div className="slide-handle" />
                 </div>
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 23148ed34..5470a72f5 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -14,7 +14,7 @@ import { ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { AudioField } from '../../../fields/URLField';
 import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
-import { emptyFunction, isTargetChildOf as isParentOf, lightOrDark, return18, returnEmptyString, returnFalse, returnTrue, returnVal, returnZero, simulateMouseClick, Utils } from '../../../Utils';
+import { emptyFunction, isTargetChildOf as isParentOf, lightOrDark, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick, Utils } from '../../../Utils';
 import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
 import { DocServer } from '../../DocServer';
 import { Docs, DocUtils } from '../../documents/Documents';
@@ -47,6 +47,7 @@ import { DocumentLinksButton } from './DocumentLinksButton';
 import './DocumentView.scss';
 import { FieldViewProps } from './FieldView';
 import { FormattedTextBox } from './formattedText/FormattedTextBox';
+import { KeyValueBox } from './KeyValueBox';
 import { LinkAnchorBox } from './LinkAnchorBox';
 import { PresEffect, PresEffectDirection } from './trails';
 import { PinProps, PresBox } from './trails/PresBox';
@@ -163,6 +164,7 @@ export interface DocumentViewSharedProps {
     docViewPath: () => DocumentView[];
     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.
     dataTransition?: string; // specifies animation transition - used by collectionPile and potentially other layout engines when changing the size of documents so that the change won't be abrupt
     styleProvider: Opt<StyleProviderFunc>;
     setTitleFocus?: () => void;
@@ -182,7 +184,7 @@ export interface DocumentViewSharedProps {
     pinToPres: (document: Doc, pinProps: PinProps) => void;
     ScreenToLocalTransform: () => Transform;
     bringToFront: (doc: Doc, sendToBack?: boolean) => void;
-    canEmbedOnDrag?: boolean;
+    dragAction?: dropActionType;
     treeViewDoc?: Doc;
     xPadding?: number;
     yPadding?: number;
@@ -194,7 +196,6 @@ export interface DocumentViewSharedProps {
     forceAutoHeight?: boolean;
     disableBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over.
     onClickScriptDisable?: 'never' | 'always'; // undefined = only when selected
-    enableDragWhenActive?: boolean;
     waitForDoubleClickToClick?: () => 'never' | 'always' | undefined;
     defaultDoubleClick?: () => 'default' | 'ignore' | undefined;
     pointerEvents?: () => Opt<string>;
@@ -314,7 +315,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     @computed get titleHeight() {
         return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.TitleHeight) || 0;
     }
-    @computed get pointerEvents(): 'none' | 'all' | 'visiblePainted' | undefined {
+    get pointerEvents(): 'none' | 'all' | 'visiblePainted' | undefined {
         return this.props.styleProvider?.(this.Document, this.props, StyleProp.PointerEvents + (this.props.isSelected() ? ':selected' : ''));
     }
     @computed get finalLayoutKey() {
@@ -355,11 +356,16 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     componentDidMount() {
         this.setupHandlers();
     }
-
+    preDropFunc = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => {
+        if (de.complete.docDragData && this.isContentActive()) {
+            targetAction && (de.complete.docDragData.dropAction = targetAction);
+            e.stopPropagation();
+        }
+    };
     setupHandlers() {
         this.cleanupHandlers(false);
         if (this._mainCont.current) {
-            this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this), this.props.Document);
+            this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this), this.props.Document, this.preDropFunc);
             this._multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this));
             this._holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this));
         }
@@ -387,7 +393,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             dragData.treeViewDoc = this.props.treeViewDoc;
             dragData.removeDocument = this.props.removeDocument;
             dragData.moveDocument = this.props.moveDocument;
-            dragData.canEmbed = this.props.canEmbedOnDrag;
+            dragData.canEmbed = this.rootDoc.dragAction ?? this.props.dragAction ? true : false;
             const ffview = this.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
             ffview && runInAction(() => (ffview.ChildDrag = this.props.DocumentView()));
             DragManager.StartDocumentDrag(
@@ -478,7 +484,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                             : '';
                         DocumentViewInternal.addDocTabFunc = oldFunc;
                     };
-                    clickFunc = () => (this.props.Document.dontUndo ? func() : UndoManager.RunInBatch(func, 'click ' + this.rootDoc.title));
+                    clickFunc = () => UndoManager.RunInBatch(func, 'click ' + this.rootDoc.title);
                 } else {
                     // 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
                     if ((this.layoutDoc.onDragStart || this.props.Document.rootDocument) && !(e.ctrlKey || e.button > 0)) {
@@ -506,7 +512,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     onPointerDown = (e: React.PointerEvent): void => {
         this._longPressSelector = setTimeout(() => {
             if (DocumentView.LongPress) {
-                if (this.rootDoc.dontUndo) {
+                if (this.rootDoc.undoIgnoreFields) {
                     runInAction(() => (UndoStack.HideInline = !UndoStack.HideInline));
                 } else {
                     this.props.select(false);
@@ -535,7 +541,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 //if (this.props.isSelected(true) && this.rootDoc.type !== DocumentType.PDF && this.layoutDoc._type_collection !== CollectionViewType.Docking) e.preventDefault();
 
                 // listen to move events if document content isn't active or document is draggable
-                if (!this.layoutDoc._lockedPosition && (!this.isContentActive() || this.props.enableDragWhenActive || this.rootDoc.enableDragWhenActive)) {
+                if (!this.layoutDoc._lockedPosition && (!this.isContentActive() || this.props.dragAction || this.rootDoc.dragAction)) {
                     document.addEventListener('pointermove', this.onPointerMove);
                 }
             }
@@ -549,7 +555,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
         if (!Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, Date.now())) {
             this.cleanupPointerEvents();
-            this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && 'embed') || ((this.Document.dropAction || this.props.dropAction || undefined) as dropActionType));
+            this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && 'embed') || ((this.Document.dragAction || this.props.dragAction || undefined) as dropActionType));
         }
     };
 
@@ -608,10 +614,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     @undoBatch
     @action
     drop = (e: Event, de: DragManager.DropEvent) => {
-        if (this.props.dontRegisterView || this.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return;
+        if (this.props.dontRegisterView || this.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return false;
         if (this.props.Document === Doc.ActiveDashboard) {
+            e.stopPropagation();
             alert((e.target as any)?.closest?.('*.lm_content') ? "You can't perform this move most likely because you don't have permission to modify the destination." : 'Linking to document tabs not yet supported. Drop link on document content.');
-            return;
+            e.preventDefault();
+            return true;
         }
         const linkdrag = de.complete.annoDragData ?? de.complete.linkDragData;
         if (linkdrag) {
@@ -625,8 +633,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     const dropDoc = de.complete.annoDragData?.dropDocument ?? this._componentView?.getAnchor?.(true) ?? this.rootDoc;
                     de.complete.linkDocument = DocUtils.MakeLink(linkdrag.linkSourceDoc, dropDoc, {}, undefined, [de.x, de.y - 50]);
                 }
+                return true;
             }
         }
+        return false;
     };
 
     @undoBatch
@@ -851,7 +861,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     setHeight = (height: number) => (this.layoutDoc._height = height);
     setContentView = action((view: { getAnchor?: (addAsAnnotation: boolean) => Doc; forward?: () => boolean; back?: () => boolean }) => (this._componentView = view));
     isContentActive = (outsideReaction?: boolean): boolean | undefined => {
-        return this.props.isContentActive() === false
+        //  true  - if the document has been activated directly or indirectly (by having its children selected)
+        //  false - if its pointer events are explicitly turned off or if it's container tells it that it's inactive
+        // undefined - it is not active, but it should be responsive to actions that might active it or its contents (eg clicking)
+        return this.props.isContentActive() === false || this.props.pointerEvents?.() === 'none' || (this.rootDoc.pointerEvents === 'none' && !StrCast(this.props.LayoutTemplateString).includes(KeyValueBox.name))
             ? false
             : Doc.ActiveTool !== InkTool.None ||
               SnappingManager.GetIsDragging() ||
@@ -871,17 +884,18 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 .some(doc => Doc.AreProtosEqual(DocCast(doc.annotationOn), this.rootDoc));
         const childOverlayed = () => Array.from(DocumentManager._overlayViews).some(view => Doc.AreProtosEqual(view.rootDoc, this.rootDoc));
         return !this.props.LayoutTemplateString &&
-            !this.props.isSelected() &&
+            !this.isContentActive() &&
             LightboxView.LightboxDoc !== this.rootDoc &&
             this.thumb &&
             !Doc.AreProtosEqual(DocumentLinksButton.StartLink, this.rootDoc) &&
-            ((!childHighlighted() && !childOverlayed() && !Doc.isBrushedHighlightedDegree(this.rootDoc)) || this.rootDoc._type_collection === CollectionViewType.Docking) &&
-            !this._componentView?.isAnyChildContentActive?.()
+            ((!childHighlighted() && !childOverlayed() && !Doc.isBrushedHighlightedDegree(this.rootDoc)) || this.rootDoc._type_collection === CollectionViewType.Docking)
             ? true
             : false;
     };
     childFilters = () => [...this.props.childFilters(), ...StrListCast(this.layoutDoc.childFilters)];
-    contentPointerEvents = () => (!this.disableClickScriptFunc && this.onClickHandler ? 'none' : this.pointerEvents);
+
+    /// disable pointer events on content when there's an enabled onClick script, or if contents are marked inactive
+    contentPointerEvents = () => ((!this.disableClickScriptFunc && this.onClickHandler) || this.isContentActive() === false ? 'none' : this.pointerEvents);
     @computed get contents() {
         TraceMobx();
         const isInk = StrCast(this.layoutDoc.layout).includes(InkingStroke.name) && !this.props.LayoutTemplateString;
@@ -889,7 +903,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             <div
                 className="documentView-contentsView"
                 style={{
-                    pointerEvents: (isInk ? 'none' : this.pointerEvents) ?? 'all',
+                    pointerEvents: (isInk ? 'none' : this.contentPointerEvents()) ?? 'all',
                     height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
                 }}>
                 {!this._retryThumb || !this.thumbShown() ? null : (
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 85dd779fc..4ebf22ddf 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -45,7 +45,7 @@ export interface FieldViewProps extends DocumentViewSharedProps {
 @observer
 export class FieldView extends React.Component<FieldViewProps> {
     public static LayoutString(fieldType: { name: string }, fieldStr: string) {
-        return `<${fieldType.name} {...props} fieldKey={'${fieldStr}'}/>`; //e.g., "<ImageBox {...props} fieldKey={"data} />"
+        return `<${fieldType.name} {...props} fieldKey={'${fieldStr}'}/>`; //e.g., "<ImageBox {...props} fieldKey={'data'} />"
     }
 
     @computed
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 9acbee1e7..64d6814df 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -387,7 +387,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                     ref={this._overlayIconRef}
                     onPointerDown={e => setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => (this.rootDoc[`_${this.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined))}
                     style={{
-                        display: (SnappingManager.GetIsDragging() && DragManager.DocDragData?.canEmbed) || DocListCast(this.dataDoc[this.fieldKey + '-alternates']).length ? 'block' : 'none',
+                        display: (this.props.isContentActive() !== false && DragManager.DocDragData?.canEmbed) || DocListCast(this.dataDoc[this.fieldKey + '-alternates']).length ? 'block' : 'none',
                         width: 'min(10%, 25px)',
                         height: 'min(10%, 25px)',
                         background: usePath === undefined ? 'white' : usePath === 'alternate' ? 'black' : 'gray',
@@ -505,7 +505,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                     }
                 })}
                 style={{
-                    display: !SnappingManager.GetIsDragging() && this.props.thumbShown?.() ? 'none' : undefined,
+                    display: !this.props.isContentActive() && this.props.thumbShown?.() ? 'none' : undefined,
                     width: this.props.PanelWidth() ? undefined : `100%`,
                     height: this.props.PanelWidth() ? undefined : `100%`,
                     pointerEvents: this.layoutDoc._lockedPosition ? 'none' : undefined,
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 5b6b0b5a7..a6712a3db 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -213,18 +213,18 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
         document.addEventListener('pointerup', this.onDividerUp);
     };
 
-    getFieldView = async () => {
+    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.props.Document.data).title}`, _chromeHidden: true });
+            const parent = Docs.Create.StackingDocument([], { _layout_autoHeight: true, _width: 300, title: `field views for ${DocCast(this.props.Document).title}`, _chromeHidden: true });
             for (const row of rows) {
-                const field = this.createFieldView(DocCast(this.props.Document.data), row);
+                const field = this.createFieldView(DocCast(this.props.Document), row);
                 field && Doc.AddDocToList(parent, 'data', field);
                 row.uncheck();
             }
             return parent;
         }
-        return rows.length ? this.createFieldView(DocCast(this.props.Document.data), rows.lastElement()) : undefined;
+        return rows.length ? this.createFieldView(DocCast(this.props.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 d0db60b37..01acdccb7 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -67,7 +67,6 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
             isSelected: returnFalse,
             setHeight: returnFalse,
             select: emptyFunction,
-            dropAction: 'embed',
             bringToFront: emptyFunction,
             renderDepth: 1,
             isContentActive: returnFalse,
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index e86b881a8..9bcd04cf5 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -47,7 +47,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
             if (separation > 100) {
                 const dragData = new DragManager.DocumentDragData([this.rootDoc]);
                 dragData.dropAction = 'embed';
-                dragData.removeDropProperties = ['link_anchor_1_x', 'link_anchor_1_y', 'link_anchor_2_x', 'link_anchor_2_y', 'onClick'];
+                dragData.dropPropertiesToRemove = ['link_anchor_1_x', 'link_anchor_1_y', 'link_anchor_2_x', 'link_anchor_2_y', 'onClick'];
                 DragManager.StartDocumentDrag([this._ref.current!], dragData, pt[0], pt[1]);
                 return true;
             } else {
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 93e54ffb7..de0b57fd7 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -512,9 +512,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return this.addDocument(doc, annotationKey);
     };
 
-    pointerEvents = () => {
-        return this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : SnappingManager.GetIsDragging() ? undefined : 'none';
-    };
+    pointerEvents = () => (this.props.isContentActive() && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : 'none');
+
     @computed get annotationLayer() {
         return (
             <div className="mapBox-annotationLayer" style={{ height: Doc.NativeHeight(this.Document) || undefined }} ref={this._annotationLayer}>
diff --git a/src/client/views/nodes/MapBox/MapBox2.tsx b/src/client/views/nodes/MapBox/MapBox2.tsx
index 72f37b62c..a9154c5bb 100644
--- a/src/client/views/nodes/MapBox/MapBox2.tsx
+++ b/src/client/views/nodes/MapBox/MapBox2.tsx
@@ -510,7 +510,7 @@ export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     };
 
     pointerEvents = () => {
-        return this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : SnappingManager.GetIsDragging() ? undefined : 'none';
+        return this.props.isContentActive() === false ? 'none' : this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : SnappingManager.GetIsDragging() ? undefined : 'none';
     };
     @computed get annotationLayer() {
         return (
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 6d040ca1a..fd4c6366b 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -559,7 +559,6 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             </div>
         );
     }
-    isPdfContentActive = () => this.isAnyChildContentActive() || this.props.isSelected() || (this.props.renderDepth === 0 && LightboxView.IsLightboxDocView(this.props.docViewPath()));
     @computed get renderPdfView() {
         TraceMobx();
         const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
@@ -592,7 +591,6 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         pdf={this._pdf}
                         focus={this.focus}
                         url={this.pdfUrl!.url.pathname}
-                        isContentActive={this.isPdfContentActive}
                         anchorMenuClick={this.anchorMenuClick}
                         loaded={!Doc.NativeAspect(this.dataDoc) ? this.loaded : undefined}
                         setPdfViewer={this.setPdfViewer}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 16705a6b3..34a1229ba 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -918,7 +918,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 ScreenToLocalTransform={this.scrollXf}
                 NativeDimScaling={returnOne}
                 focus={this.focus}
-                dropAction="embed"
                 childFilters={childFilters}
                 select={emptyFunction}
                 isAnyChildContentActive={returnFalse}
@@ -1003,8 +1002,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         }
         return this.props.styleProvider?.(doc, props, property);
     };
-    pointerEvents = () => (!this._draggingSidebar && this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && !MarqueeOptionsMenu.Instance?.isShown() ? 'all' : SnappingManager.GetIsDragging() ? undefined : 'none');
-    annotationPointerEvents = () => (this._isAnnotating || SnappingManager.GetIsDragging() || Doc.ActiveTool !== InkTool.None ? 'all' : 'none');
+    pointerEvents = () =>
+        !this._draggingSidebar && this.props.isContentActive() && !MarqueeOptionsMenu.Instance?.isShown()
+            ? 'all' //
+            : 'none';
+    annotationPointerEvents = () => (this.props.isContentActive() && (this._isAnnotating || SnappingManager.GetIsDragging() || Doc.ActiveTool !== InkTool.None) ? 'all' : 'none');
     render() {
         const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
         const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this.props.pointerEvents?.() as any);
@@ -1013,7 +1015,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             <div
                 className="webBox"
                 ref={this._mainCont}
-                style={{ pointerEvents: this.pointerEvents(), position: SnappingManager.GetIsDragging() ? 'absolute' : undefined, display: !SnappingManager.GetIsDragging() && this.props.thumbShown?.() ? 'none' : undefined }}>
+                style={{
+                    pointerEvents: this.pointerEvents(), //
+                    position: SnappingManager.GetIsDragging() ? 'absolute' : undefined,
+                    display: !this.props.isContentActive() && this.props.thumbShown?.() ? 'none' : undefined,
+                }}>
                 <div className="webBox-background" style={{ backgroundColor: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor) }} />
                 <div
                     className="webBox-container"
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 079129dc9..2cd3c6cd6 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -297,7 +297,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     };
 
     dispatchTransaction = (tx: Transaction) => {
-        if (this._editorView) {
+        if (this._editorView && (this._editorView as any).docView) {
             const state = this._editorView.state.apply(tx);
             this._editorView.updateState(state);
 
@@ -2020,17 +2020,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     _oldWheel: any;
     render() {
         TraceMobx();
-        const active = this.props.isContentActive() || this.props.isSelected();
-        const selected = active;
+        const active = this.props.isContentActive();
         const scale = (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1);
         const rounded = StrCast(this.layoutDoc.layout_borderRounding) === '100%' ? '-rounded' : '';
-        const interactive = (Doc.ActiveTool === InkTool.None || SnappingManager.GetIsDragging()) && (this.layoutDoc.z || !this.layoutDoc._lockedPosition);
-        if (!selected && FormattedTextBoxComment.textBox === this) setTimeout(FormattedTextBoxComment.Hide);
+        if (!active && FormattedTextBoxComment.textBox === this) setTimeout(FormattedTextBoxComment.Hide);
         const minimal = this.props.ignoreAutoHeight;
         const paddingX = NumCast(this.layoutDoc._xMargin, this.props.xPadding || 0);
         const paddingY = NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0);
-        const selPad = (selected && !this.layoutDoc._createDocOnCR) || minimal ? Math.min(paddingY, Math.min(paddingX, 10)) : 0;
-        const selPaddingClass = selected && !this.layoutDoc._createDocOnCR && paddingY >= 10 ? '-selected' : '';
+        const selPad = (active && !this.layoutDoc._createDocOnCR) || minimal ? Math.min(paddingY, Math.min(paddingX, 10)) : 0;
+        const selPaddingClass = active && !this.layoutDoc._createDocOnCR && paddingY >= 10 ? '-selected' : '';
         const styleFromLayoutString = Doc.styleFromLayoutString(this.rootDoc, this.layoutDoc, this.props, scale); // this converts any expressions in the format string to style props.  e.g., <FormattedTextBox height='{this._headerHeight}px' >
         return styleFromLayoutString?.height === '0px' ? null : (
             <div
@@ -2051,7 +2049,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                               width: `${100 / scale}%`,
                               height: `${100 / scale}%`,
                           }),
-                    display: !SnappingManager.GetIsDragging() && this.props.thumbShown?.() ? 'none' : undefined,
+                    display: !this.props.isContentActive() && this.props.thumbShown?.() ? 'none' : undefined,
                     transition: 'inherit',
                     // overflowY: this.layoutDoc._layout_autoHeight ? "hidden" : undefined,
                     color: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color),
@@ -2066,7 +2064,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     style={{
                         overflow: this.layout_autoHeight && this.props.CollectionFreeFormDocumentView?.() ? 'hidden' : undefined, //x this breaks viewing an layout_autoHeight doc in its own tab, or in the lightbox
                         height: this.props.height || (this.layout_autoHeight && this.props.renderDepth && !this.props.suppressSetHeight ? 'max-content' : undefined),
-                        pointerEvents: interactive ? undefined : 'none',
+                        pointerEvents: Doc.ActiveTool === InkTool.None ? undefined : 'none',
                     }}
                     onContextMenu={this.specificContextMenu}
                     onKeyDown={this.onKeyDown}
@@ -2078,11 +2076,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     onPointerDown={this.onPointerDown}
                     onDoubleClick={this.onDoubleClick}>
                     <div
-                        className={`formattedTextBox-outer${selected ? '-selected' : ''}`}
+                        className={`formattedTextBox-outer${active ? '-selected' : ''}`}
                         ref={this._scrollRef}
                         style={{
                             width: this.props.dontSelectOnLoad ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`,
-                            pointerEvents: !active && !SnappingManager.GetIsDragging() ? 'none' : undefined,
                             overflow: this.layoutDoc._createDocOnCR ? 'hidden' : this.layoutDoc._layout_autoHeight ? 'visible' : undefined,
                         }}
                         onScroll={this.onScroll}
@@ -2096,7 +2093,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                                 paddingRight: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX - selPad}px`),
                                 paddingTop: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY - selPad}px`),
                                 paddingBottom: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY - selPad}px`),
-                                pointerEvents: !active && !SnappingManager.GetIsDragging() ? (IsFollowLinkScript(this.layoutDoc.onClick) ? 'none' : undefined) : undefined,
+                                // pointerEvents: !active && IsFollowLinkScript(this.layoutDoc.onClick) ? 'none' : undefined,
                             }}
                         />
                     </div>
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 4f7d2e1c1..ef1c3911c 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -1029,9 +1029,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.rootDoc, this.fieldKey, doc);
     getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65); // listBox padding-left and pres-box-cont minHeight
     panelHeight = () => this.props.PanelHeight() - 40;
-    isContentActive = (outsideReaction?: boolean) => this.props.isContentActive(outsideReaction);
-    //.ActiveTool === InkTool.None && !this.layoutDoc._lockedPosition && (this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false;
-
     /**
      * For sorting the array so that the order is maintained when it is dropped.
      */
@@ -2462,6 +2459,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 childIgnoreNativeSize={true}
                                 moveDocument={returnFalse}
                                 ignoreUnrendered={true}
+                                childDragAction="move"
                                 //childLayoutFitWidth={returnTrue}
                                 childOpacity={returnOne}
                                 childClickScript={PresBox.navigateToDocScript}
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 34e069046..f31cf6147 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -192,13 +192,14 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const dragArray = this.presBoxView?._dragArray ?? [];
         const dragData = new DragManager.DocumentDragData(this.presBoxView?.sortArray() ?? []);
         if (!dragData.draggedDocuments.length) dragData.draggedDocuments.push(this.rootDoc);
-        dragData.dropAction = 'move';
         dragData.treeViewDoc = this.presBox?._type_collection === CollectionViewType.Tree ? this.presBox : undefined; // this.props.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);
             }
@@ -212,16 +213,19 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
             dragItem.push(doc);
         }
 
-        // const dropEvent = () => runInAction(() => this._dragging = false);
         if (activeItem) {
+            runInAction(() => (this._dragging = true));
             DragManager.StartDocumentDrag(
                 dragItem.map(ele => ele),
                 dragData,
                 e.clientX,
                 e.clientY,
-                undefined
+                undefined,
+                action(() => {
+                    Array.from(classesToRestore).forEach(pair => (pair[0].className = pair[1]));
+                    this._dragging = false;
+                })
             );
-            // runInAction(() => this._dragging = true);
             return true;
         }
         return false;
@@ -536,7 +540,6 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             <div
                                 className="presItem-number"
                                 title="select without navigation"
-                                style={{ pointerEvents: this.presBoxView?.isContentActive() ? 'all' : undefined }}
                                 onPointerDown={e => {
                                     e.stopPropagation();
                                     if (this._itemRef.current && this._dragRef.current) {
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index dd202418b..8f6b8cd41 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -483,7 +483,10 @@ export class PDFViewer extends React.Component<IViewerProps> {
         }
     };
 
-    pointerEvents = () => (this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : SnappingManager.GetIsDragging() ? undefined : 'none');
+    pointerEvents = () =>
+        this.props.isContentActive() && !MarqueeOptionsMenu.Instance.isShown()
+            ? 'all' //
+            : 'none';
     @computed get annotationLayer() {
         return (
             <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this.props.Document), transform: `scale(${NumCast(this.props.layoutDoc._freeform_scale, 1)})` }} ref={this._annotationLayer}>
@@ -514,10 +517,10 @@ export class PDFViewer extends React.Component<IViewerProps> {
     panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1);
     panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
     transparentFilter = () => [...this.props.childFilters(), Utils.IsTransparentFilter()];
-    opaqueFilter = () => [...this.props.childFilters(), Utils.noDragsDocFilter, ...(DragManager.docsBeingDragged.length ? [] : [Utils.IsOpaqueFilter()])];
+    opaqueFilter = () => [...this.props.childFilters(), Utils.noDragsDocFilter, ...(DragManager.docsBeingDragged.length && this.props.isContentActive() ? [] : [Utils.IsOpaqueFilter()])];
     childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
         if (doc instanceof Doc && property === StyleProp.PointerEvents) {
-            if (this.inlineTextAnnotations.includes(doc)) return 'none';
+            if (this.inlineTextAnnotations.includes(doc) || this.props.isContentActive() === false) return 'none';
             return 'all';
         }
         return this.props.styleProvider?.(doc, props, property);
@@ -536,8 +539,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
                 NativeWidth={returnZero}
                 NativeHeight={returnZero}
                 setContentView={emptyFunction} // override setContentView to do nothing
-                pointerEvents={SnappingManager.GetIsDragging() ? returnAll : returnNone} // freeform view doesn't get events unless something is being dragged onto it.
-                childPointerEvents="all" // but freeform children need to get events to allow text editing, etc
+                pointerEvents={SnappingManager.GetIsDragging() && this.props.isContentActive() ? returnAll : returnNone} // freeform view doesn't get events unless something is being dragged onto it.
+                childPointerEvents={this.props.isContentActive() !== false ? 'all' : 'none'} // but freeform children need to get events to allow text editing, etc
                 renderDepth={this.props.renderDepth + 1}
                 isAnnotationOverlay={true}
                 fieldKey={this.props.fieldKey + '_annotations'}
@@ -549,7 +552,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
                 ScreenToLocalTransform={this.overlayTransform}
                 isAnyChildContentActive={returnFalse}
                 isAnnotationOverlayScrollable={true}
-                dropAction="embed"
                 childFilters={childFilters}
                 select={emptyFunction}
                 bringToFront={emptyFunction}
@@ -558,14 +560,14 @@ export class PDFViewer extends React.Component<IViewerProps> {
         </div>
     );
     @computed get overlayTransparentAnnotations() {
-        return this.renderAnnotations(this.transparentFilter, 'multiply', DragManager.docsBeingDragged.length ? 'none' : undefined);
+        return this.renderAnnotations(this.transparentFilter, 'multiply', DragManager.docsBeingDragged.length && this.props.isContentActive() ? 'none' : undefined);
     }
     @computed get overlayOpaqueAnnotations() {
         return this.renderAnnotations(this.opaqueFilter, this.allAnnotations.some(anno => anno.mixBlendMode) ? 'hard-light' : undefined);
     }
     @computed get overlayLayer() {
         return (
-            <div style={{ pointerEvents: SnappingManager.GetIsDragging() ? 'all' : 'none' }}>
+            <div style={{ pointerEvents: this.props.isContentActive() && SnappingManager.GetIsDragging() ? 'all' : 'none' }}>
                 {this.overlayTransparentAnnotations}
                 {this.overlayOpaqueAnnotations}
             </div>
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index c674a20d2..c9f7e4114 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -776,7 +776,7 @@ export namespace Doc {
                 linkMap.set(link[Id], await Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks));
             }
         });
-        Doc.SetInPlace(copy, 'title', 'CLONE: ' + doc.title, true);
+        Doc.SetInPlace(copy, 'title', '>:' + doc.title, true);
         copy.cloneOf = doc;
         cloneMap.set(doc[Id], copy);
 
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index 757d507be..76b287be7 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -95,12 +95,12 @@ export const documentSchema = createSchema({
     link_displayArrow: 'boolean', // toggles directed arrows
 
     // drag drop properties
-    _stayInCollection: 'boolean', // whether document can be dropped into a different collection
+    _dragOnlyWithinContainer: 'boolean', // whether document can be dropped into a different collection
     dragFactory: Doc, // the document that serves as the "template" for the onDragStart script.  ie, to drag out copies of the dragFactory document.
-    dropAction: 'string', // override specifying what should happen when this document is dropped (can be "embed", "copy", "move")
-    targetDropAction: 'string', // allows the target of a drop event to specify the dropAction ("embed", "copy", "move") NOTE: if the document is dropped within the same collection, the dropAction is coerced to 'move'
-    childDropAction: 'string', // specify the override for what should happen when the child of a collection is dragged from it and dropped (can be "embed" or "copy")
-    removeDropProperties: listSpec('string'), // properties that should be removed from the embed/copy/etc of this document when it is dropped
+    dropAction: 'string', // override specifying what should happen when something is dropped on this document (can be "embed", "copy", "move")
+    dragAction: 'string', // override specifying what should happen when this document s dragged (can be "embed", "copy", "move")
+    childDragAction: 'string', // specify the override for what should happen when the child of a collection is dragged from it and dropped (can be "embed" or "copy")
+    dropPropertiesToRemove: listSpec('string'), // properties that should be removed from the embed/copy/etc of this document when it is dropped
 });
 
 export const collectionSchema = createSchema({
diff --git a/src/fields/util.ts b/src/fields/util.ts
index f365adf4b..e3d1abc53 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -7,7 +7,7 @@ import { SerializationHelper } from '../client/util/SerializationHelper';
 import { UndoManager } from '../client/util/UndoManager';
 import { returnZero } from '../Utils';
 import CursorField from './CursorField';
-import { aclLevel, Doc, DocListCast, DocListCastAsync, HierarchyMapping, ReverseHierarchyMap, updateCachedAcls } from './Doc';
+import { aclLevel, Doc, DocListCast, DocListCastAsync, HierarchyMapping, ReverseHierarchyMap, StrListCast, updateCachedAcls } from './Doc';
 import { AclAdmin, AclEdit, AclPrivate, AclSelfEdit, DocAcl, DocData, DocLayout, FieldKeys, ForceServerWrite, Height, Initializing, SelfProxy, Update, UpdatingFromServer, Width } from './DocSymbols';
 import { Id, OnUpdate, Parent, ToValue } from './FieldSymbols';
 import { List } from './List';
@@ -86,7 +86,7 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
             DocServer.registerDocWithCachedUpdate(receiver, prop as string, curValue);
         }
         !receiver[Initializing] &&
-            !receiver.dontUndo &&
+            !StrListCast(receiver.undoIgnoreFields).includes(prop.toString()) &&
             (!receiver[UpdatingFromServer] || receiver[ForceServerWrite]) &&
             UndoManager.AddEvent(
                 {
-- 
cgit v1.2.3-70-g09d2


From 1429ab79eac9aa316082f52c14c576f6b3a97111 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 23 Jun 2023 13:14:18 -0400
Subject: cleaned up more issues with pointer events/contents active.  fixed
 dragging from piles.

---
 src/client/documents/Documents.ts                  |  7 ++-
 src/client/util/UndoManager.ts                     |  3 +-
 src/client/views/DocComponent.tsx                  |  2 +-
 src/client/views/PropertiesButtons.tsx             |  1 +
 .../collections/CollectionMasonryViewFieldRow.tsx  |  9 +--
 src/client/views/collections/CollectionMenu.tsx    |  3 +-
 .../views/collections/CollectionNoteTakingView.tsx | 13 +++--
 .../collections/CollectionNoteTakingViewColumn.tsx |  1 +
 .../views/collections/CollectionPileView.tsx       |  8 +--
 .../collections/CollectionStackedTimeline.tsx      | 30 +++++-----
 .../views/collections/CollectionStackingView.tsx   | 10 +++-
 .../CollectionStackingViewFieldColumn.tsx          |  1 +
 src/client/views/collections/CollectionSubView.tsx | 18 +++---
 .../views/collections/CollectionTreeView.tsx       | 15 +++--
 src/client/views/collections/TreeView.tsx          | 23 ++++++--
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 17 +++---
 .../collections/collectionFreeForm/MarqueeView.tsx |  1 +
 .../CollectionMulticolumnView.tsx                  |  1 +
 .../CollectionMultirowView.tsx                     |  1 +
 .../collectionSchema/CollectionSchemaView.tsx      |  3 +-
 src/client/views/nodes/ComparisonBox.tsx           | 11 ++--
 src/client/views/nodes/DocumentView.tsx            | 22 +++++---
 src/client/views/nodes/FunctionPlotBox.tsx         |  5 +-
 src/client/views/nodes/ImageBox.tsx                | 17 +++---
 src/client/views/nodes/LabelBox.tsx                |  2 +
 src/client/views/nodes/ScriptingBox.tsx            |  8 ++-
 .../views/nodes/formattedText/FormattedTextBox.tsx | 64 +++++++++++++---------
 src/fields/Proxy.ts                                |  4 +-
 src/fields/util.ts                                 |  3 +-
 29 files changed, 184 insertions(+), 119 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index a61ef1da3..acd323eca 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -361,12 +361,13 @@ export class DocumentOptions {
     _forceActive?: BOOLt = new BoolInfo('flag to handle pointer events when not selected (or otherwise active)');
     _dragOnlyWithinContainer?: BOOLt = new BoolInfo('whether the document should remain in its collection when someone tries to drag and drop it elsewhere');
     _raiseWhenDragged?: BOOLt = new BoolInfo('whether a document is brought to front when dragged.');
-    childDragAction?: DROPt = new DAInfo('what should happen to the child documents when they are dragged fromt he collection');
+    childDragAction?: DROPt = new DAInfo('what should happen to the child documents when they are dragged from the collection');
     dropConverter?: ScriptField; // script to run when documents are dropped on this Document.
     dropAction?: DROPt = new DAInfo("what should happen to this document when it's dropped somewhere else");
     _dropAction?: DROPt = new DAInfo("what should happen to this document when it's dropped somewhere else");
     _dropPropertiesToRemove?: List<string>; // list of properties that should be removed from a document when it is dropped.  e.g., a creator button may be forceActive to allow it be dragged, but the forceActive property can be removed from the dropped document
     cloneFieldFilter?: List<string>; // fields not to copy when the document is clonedclipboard?: Doc;
+    dragWhenActive?: BOOLt = new BoolInfo('should document drag when it is active - e.g., pileView, group');
     dragAction?: DROPt = new DAInfo('how to drag document when it is active (e.g., tree, groups)');
     dragFactory_count?: NUMt = new NumInfo('number of items created from a drag button (used for setting title with incrementing index)', true);
     dragFactory?: Doc; // document to create when dragging with a suitable onDragStart script
@@ -667,7 +668,7 @@ export namespace Docs {
                 {
                     data: new List<Doc>(),
                     layout: { view: EmptyBox, dataField: defaultDataKey },
-                    options: { childDragAction: 'embed', title: 'Global Group Database' },
+                    options: { title: 'Global Group Database' },
                 },
             ],
             [
@@ -1663,7 +1664,7 @@ export namespace DocUtils {
             });
         });
         if (create) {
-            const newCollection = Docs.Create.PileDocument(docList, { title: 'pileup', x: (x || 0) - size, y: (y || 0) - size, _width: size * 2, _height: size * 2 });
+            const newCollection = Docs.Create.PileDocument(docList, { title: 'pileup', x: (x || 0) - size, y: (y || 0) - size, _width: size * 2, _height: size * 2, dragWhenActive: true });
             newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - size;
             newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - size;
             newCollection._width = newCollection._height = size * 2;
diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts
index 2379cb2ab..b59af6656 100644
--- a/src/client/util/UndoManager.ts
+++ b/src/client/util/UndoManager.ts
@@ -1,4 +1,5 @@
 import { observable, action, runInAction } from 'mobx';
+import { Field } from '../../fields/Doc';
 import { Without } from '../../Utils';
 
 function getBatchName(target: any, key: string | symbol): string {
@@ -95,7 +96,7 @@ export namespace UndoManager {
 
     export function AddEvent(event: UndoEvent, value?: any): void {
         if (currentBatch && batchCounter.get() && !undoing) {
-            console.log('                         '.slice(0, batchCounter.get()) + 'UndoEvent : ' + event.prop + ' = ' + value);
+            console.log('                         '.slice(0, batchCounter.get()) + 'UndoEvent : ' + event.prop + ' = ' + (value instanceof Array ? value.map(val => Field.toScriptString(val)).join(',') : Field.toScriptString(value)));
             currentBatch.push(event);
             tempEvents?.push(event);
         }
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 0709d6cb9..3de40a640 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -220,7 +220,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
                             Doc.ActiveDashboard && inheritParentAcls(Doc.ActiveDashboard, doc);
                         });
                     const annoDocs = targetDataDoc[annotationKey ?? this.annotationKey] as List<Doc>;
-                    if (annoDocs instanceof List) annoDocs.push(...added);
+                    if (annoDocs instanceof List) annoDocs.push(...added.filter(add => !annoDocs.includes(add)));
                     else targetDataDoc[annotationKey ?? this.annotationKey] = new List<Doc>(added);
                     targetDataDoc[(annotationKey ?? this.annotationKey) + '_modificationDate'] = new DateField(new Date(Date.now()));
                 }
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 11b89fd69..74dd1c2f7 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -209,6 +209,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
             (dv, doc) => {
                 doc.isGroup = !doc.isGroup;
                 doc.forceActive = doc.isGroup;
+                doc.dragWhenActive = doc.isGroup;
             }
         );
     }
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index 64f9c6a87..6f88f6727 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -98,14 +98,15 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
     rowDrop = action((e: Event, de: DragManager.DropEvent) => {
         this._createEmbeddingSelected = false;
         if (de.complete.docDragData) {
-            this.props.parent.Document.dropConverter instanceof ScriptField && this.props.parent.Document.dropConverter.script.run({ dragData: de.complete.docDragData });
             const key = this.props.pivotField;
             const castedValue = this.getValue(this.heading);
             const onLayoutDoc = this.onLayoutDoc(key);
-            de.complete.docDragData.droppedDocuments.forEach(d => Doc.SetInPlace(d, key, castedValue, !onLayoutDoc));
-            this.props.parent.onInternalDrop(e, de);
-            e.stopPropagation();
+            if (this.props.parent.onInternalDrop(e, de)) {
+                de.complete.docDragData.droppedDocuments.forEach(d => Doc.SetInPlace(d, key, castedValue, !onLayoutDoc));
+            }
+            return true;
         }
+        return false;
     });
 
     getValue = (value: string): any => {
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index fc3f713ce..7d71bce13 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -427,8 +427,9 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
         if (docDragData?.draggedDocuments.length) {
             this._buttonizableCommands?.filter(c => c.title === this._currentKey).map(c => c.immediate(docDragData.draggedDocuments || []));
             e.stopPropagation();
+            return true;
         }
-        return true;
+        return false;
     }
 
     dragViewDown = (e: React.PointerEvent) => {
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index 9a3ab8074..f1c4c2cdf 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -441,23 +441,24 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                         docs.splice(previousDocIndex + 1, 0, ...newDocs);
                     }
                 }
+                return true;
             }
         } else if (de.complete.linkDragData?.dragDocument.embedContainer === this.props.Document && de.complete.linkDragData?.linkDragView?.props.CollectionFreeFormDocumentView?.()) {
             const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, _layout_fitWidth: true, title: 'dropped annotation' });
-            this.props.addDocument?.(source);
+            if (!this.props.addDocument?.(source)) e.preventDefault();
             de.complete.linkDocument = DocUtils.MakeLink(source, de.complete.linkDragData.linkSourceGetAnchor(), { link_relationship: 'doc annotation' }); // TODODO this is where in text links get passed
             e.stopPropagation();
-        } else if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de.complete.annoDragData);
+            return true;
+        } else if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) {
+            return this.internalAnchorAnnoDrop(e, de.complete.annoDragData);
+        }
         return false;
     };
 
     @undoBatch
     internalAnchorAnnoDrop(e: Event, annoDragData: DragManager.AnchorAnnoDragData) {
         const dropCreator = annoDragData.dropDocCreator;
-        annoDragData.dropDocCreator = (annotationOn: Doc | undefined) => {
-            const dropDoc = dropCreator(annotationOn);
-            return dropDoc || this.rootDoc;
-        };
+        annoDragData.dropDocCreator = (annotationOn: Doc | undefined) => dropCreator(annotationOn) || this.rootDoc;
         return true;
     }
 
diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
index 2f28ecd00..3286d60bd 100644
--- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
@@ -92,6 +92,7 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
     columnDrop = action((e: Event, de: DragManager.DropEvent) => {
         const drop = { docs: de.complete.docDragData?.droppedDocuments, val: this.getValue(this._heading) };
         drop.docs?.forEach(d => Doc.SetInPlace(d, this.props.pivotField, drop.val, false));
+        return true;
     });
 
     getValue = (value: string): any => {
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index 15570b195..91be31289 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -63,12 +63,14 @@ export class CollectionPileView extends CollectionSubView() {
             <div className="collectionPileView-innards" style={{ pointerEvents: this.contentEvents }}>
                 <CollectionFreeFormView
                     {...this.props} //
-                    childContentsActive={returnFalse}
                     layoutEngine={this.layoutEngine}
                     addDocument={this.addPileDoc}
+                    moveDocument={this.removePileDoc}
+                    // pile children never have their contents active, but will be document active whenever the entire pile is.
+                    childContentsActive={returnFalse}
+                    childDocumentsActive={this.props.isDocumentActive}
                     childDragAction="move"
                     childClickScript={this.toggleIcon}
-                    moveDocument={this.removePileDoc}
                 />
             </div>
         );
@@ -106,7 +108,6 @@ export class CollectionPileView extends CollectionSubView() {
     _undoBatch: UndoManager.Batch | undefined;
     pointerDown = (e: React.PointerEvent) => {
         let dist = 0;
-        SnappingManager.SetIsDragging(true);
         setupMoveUpEvents(
             this,
             e,
@@ -129,7 +130,6 @@ export class CollectionPileView extends CollectionSubView() {
             () => {
                 this._undoBatch?.end();
                 this._undoBatch = undefined;
-                SnappingManager.SetIsDragging(false);
             },
             emptyFunction,
             e.shiftKey && this.layoutEngine() === computePassLayout.name,
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index e835fa659..ad84d859d 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -369,22 +369,22 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
     // handles dragging and dropping markers in timeline
     @action
     internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number) {
-        if (!de.embedKey && this.props.Document._isGroup) return false;
-        if (!super.onInternalDrop(e, de)) return false;
-
-        // determine x coordinate of drop and assign it to the documents being dragged  --- see internalDocDrop of collectionFreeFormView.tsx for how it's done when dropping onto a 2D freeform view
-        const localPt = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y);
-        const x = localPt[0] - docDragData.offset[0];
-        const timelinePt = this.toTimeline(x + this._scroll, this.timelineContentWidth);
-        docDragData.droppedDocuments.forEach(drop => {
-            const anchorEnd = this.anchorEnd(drop);
-            if (anchorEnd !== undefined) {
-                Doc.SetInPlace(drop, drop._timecodeToHide === undefined ? this.props.endTag : 'timecodeToHide', timelinePt + anchorEnd - this.anchorStart(drop), false);
-            }
-            Doc.SetInPlace(drop, drop._timecodeToShow === undefined ? this.props.startTag : 'timecodeToShow', timelinePt, false);
-        });
+        if (super.onInternalDrop(e, de)) {
+            // determine x coordinate of drop and assign it to the documents being dragged  --- see internalDocDrop of collectionFreeFormView.tsx for how it's done when dropping onto a 2D freeform view
+            const localPt = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y);
+            const x = localPt[0] - docDragData.offset[0];
+            const timelinePt = this.toTimeline(x + this._scroll, this.timelineContentWidth);
+            docDragData.droppedDocuments.forEach(drop => {
+                const anchorEnd = this.anchorEnd(drop);
+                if (anchorEnd !== undefined) {
+                    Doc.SetInPlace(drop, drop._timecodeToHide === undefined ? this.props.endTag : 'timecodeToHide', timelinePt + anchorEnd - this.anchorStart(drop), false);
+                }
+                Doc.SetInPlace(drop, drop._timecodeToShow === undefined ? this.props.startTag : 'timecodeToShow', timelinePt, false);
+            });
 
-        return true;
+            return true;
+        }
+        return false;
     }
 
     onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 8eed4f716..4756b4cd3 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -459,18 +459,24 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 const docs = this.childDocList;
                 // still figuring out where to add the document
                 if (docs && newDocs.length) {
+                    newDocs.forEach(newdoc => docs.indexOf(newdoc) !== -1 && docs.splice(docs.indexOf(newdoc), 1));
                     const insertInd = dropInd === -1 ? docs.length : dropInd + dropAfter;
                     const offset = newDocs.reduce((off, ndoc) => (this.filteredChildren.find((fdoc, i) => ndoc === fdoc && i < insertInd) ? off + 1 : off), 0);
                     newDocs.filter(ndoc => docs.indexOf(ndoc) !== -1).forEach(ndoc => docs.splice(docs.indexOf(ndoc), 1));
                     docs.splice(insertInd - offset, 0, ...newDocs);
                 }
+                return true;
             }
         } else if (de.complete.linkDragData?.dragDocument.embedContainer === this.props.Document && de.complete.linkDragData?.linkDragView?.props.CollectionFreeFormDocumentView?.()) {
             const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, _layout_fitWidth: true, title: 'dropped annotation' });
-            this.props.addDocument?.(source);
+            if (!this.props.addDocument?.(source)) e.preventDefault();
             de.complete.linkDocument = DocUtils.MakeLink(source, de.complete.linkDragData.linkSourceGetAnchor(), { link_relationship: 'doc annotation' }); // TODODO this is where in text links get passed
             e.stopPropagation();
-        } else if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de.complete.annoDragData);
+            return true;
+        } else if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) {
+            return this.internalAnchorAnnoDrop(e, de.complete.annoDragData);
+        }
+        e.preventDefault();
         return false;
     };
 
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 6be9cb72d..ebb4ba5a1 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -94,6 +94,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
     columnDrop = action((e: Event, de: DragManager.DropEvent) => {
         const drop = { docs: de.complete.docDragData?.droppedDocuments, val: this.getValue(this._heading) };
         this.props.pivotField && drop.docs?.forEach(d => Doc.SetInPlace(d, this.props.pivotField, drop.val, false));
+        return true;
     });
     getValue = (value: string): any => {
         const parsed = parseInt(value);
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 539dde7e0..39fb2db1e 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -185,12 +185,14 @@ export function CollectionSubView<X>(moreProps?: X) {
         @undoBatch
         protected onGesture(e: Event, ge: GestureUtils.GestureEvent) {}
 
-        protected onInternalPreDrop(e: Event, de: DragManager.DropEvent, targetAction: dropActionType) {
+        protected onInternalPreDrop(e: Event, de: DragManager.DropEvent) {
             if (de.complete.docDragData) {
-                // if dropAction is, say 'embed', but we're just dragging within a collection, we want to ignore the targetAction.
-                // otherwise, the targetAction should become the actual action (which can still be overridden by the userDropAction -eg, shift/ctrl keys)
-                if (targetAction && !de.complete.docDragData.draggedDocuments.some(d => d.embedContainer === this.props.Document && this.childDocs.includes(d))) {
-                    de.complete.docDragData.dropAction = targetAction;
+                // override the dropEvent's dropAction
+                const dropAction = this.layoutDoc.dropAction as dropActionType;
+                // if the dropEvent's dragAction is, say 'embed', but we're just dragging within a collection, we may not actually want to make an embedding.
+                // so we check if our collection has a dropAction set on it and if so, we use that instead.
+                if (dropAction && !de.complete.docDragData.draggedDocuments.some(d => d.embedContainer === this.props.Document && this.childDocs.includes(d))) {
+                    de.complete.docDragData.dropAction = dropAction;
                 }
                 e.stopPropagation();
             }
@@ -218,16 +220,16 @@ export function CollectionSubView<X>(moreProps?: X) {
                         ScriptCast(this.rootDoc.dropConverter)?.script.run({ dragData: docDragData });
                         added = addedDocs.length ? this.addDocument(addedDocs) : true;
                     }
-                    added && e.stopPropagation();
-                    return added;
                 } else {
                     ScriptCast(this.rootDoc.dropConverter)?.script.run({ dragData: docDragData });
                     added = this.addDocument(docDragData.droppedDocuments);
+                    !added && alert('You cannot perform this move');
                 }
-                !added && alert('You cannot perform this move');
+                !added && e.preventDefault();
                 e.stopPropagation();
                 return added;
             } else if (de.complete.annoDragData) {
+                e.stopPropagation();
                 const dropCreator = de.complete.annoDragData.dropDocCreator;
                 de.complete.annoDragData.dropDocCreator = () => {
                     const dropped = dropCreator(this.props.isAnnotationOverlay ? this.rootDoc : undefined);
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 7598bb753..4cd3885f5 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -140,11 +140,16 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         if ((this._mainEle = ele)) this._treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.doc, this.onInternalPreDrop.bind(this));
     };
 
-    protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => {
+    protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent) => {
+        const dropAction = this.layoutDoc.dropAction as dropActionType;
         const dragData = de.complete.docDragData;
         if (dragData) {
-            const isInTree = () => Doc.AreProtosEqual(dragData.treeViewDoc, this.props.Document) || dragData.draggedDocuments.some(d => d.embedContainer === this.doc && this.childDocs.includes(d));
-            dragData.dropAction = targetAction && !isInTree() ? targetAction : this.doc === dragData?.treeViewDoc ? 'same' : dragData.dropAction;
+            const sameTree = Doc.AreProtosEqual(dragData.treeViewDoc, this.rootDoc) ? true : false;
+            const isAlreadyInTree = () => sameTree || dragData.draggedDocuments.some(d => d.embedContainer === this.doc && this.childDocs.includes(d));
+            if (isAlreadyInTree() !== sameTree) {
+                console.log('WHAAAT');
+            }
+            dragData.dropAction = dropAction && !isAlreadyInTree() ? dropAction : sameTree ? 'same' : dragData.dropAction;
         }
     };
 
@@ -394,6 +399,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
                 <div
                     className="collectionTreeView-contents"
                     key="tree"
+                    ref={r => !this.doc.treeViewHasOverlay && r && this.createTreeDropTarget(r)}
                     style={{
                         ...(!titleBar ? { marginLeft: this.marginX(), paddingTop: this.marginTop() } : {}),
                         overflow: 'auto',
@@ -417,8 +423,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
                                 minHeight: '100%',
                             }}
                             onWheel={e => e.stopPropagation()}
-                            onDrop={this.onTreeDrop}
-                            ref={r => !this.doc.treeViewHasOverlay && r && this.createTreeDropTarget(r)}>
+                            onDrop={this.onTreeDrop}>
                             <ul className={`no-indent${this.outlineMode ? '-outline' : ''}`}>{this.treeViewElements}</ul>
                         </div>
                     </div>
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 91ae2b3cc..8d8d895c6 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -347,7 +347,7 @@ export class TreeView extends React.Component<TreeViewProps> {
     };
     deleteItem = () => this.props.removeDoc?.(this.doc);
 
-    preTreeDrop = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => {
+    preTreeDrop = (e: Event, de: DragManager.DropEvent) => {
         const dragData = de.complete.docDragData;
         dragData && (dragData.dropAction = this.props.treeView.props.Document === dragData.treeViewDoc ? 'same' : dragData.dropAction);
     };
@@ -355,7 +355,7 @@ export class TreeView extends React.Component<TreeViewProps> {
     @undoBatch
     treeDrop = (e: Event, de: DragManager.DropEvent) => {
         const pt = [de.x, de.y];
-        if (!this._header.current) return;
+        if (!this._header.current) return false;
         const rect = this._header.current.getBoundingClientRect();
         const before = pt[1] < rect.top + rect.height / 2;
         const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocs?.length ? true : false);
@@ -364,14 +364,25 @@ export class TreeView extends React.Component<TreeViewProps> {
             const destDoc = this.doc;
             DocUtils.MakeLink(sourceDoc, destDoc, { link_relationship: 'tree link' });
             e.stopPropagation();
+            return true;
         }
         const docDragData = de.complete.docDragData;
         if (docDragData && pt[0] < rect.left + rect.width) {
             if (docDragData.draggedDocuments[0] === this.doc) return true;
-            if (this.dropDocuments(docDragData.droppedDocuments, before, inside, docDragData.dropAction, docDragData.removeDocument, docDragData.moveDocument, docDragData.treeViewDoc === this.props.treeView.props.Document)) {
-                e.stopPropagation();
-            }
+            const added = this.dropDocuments(
+                docDragData.droppedDocuments, //
+                before,
+                inside,
+                docDragData.dropAction,
+                docDragData.removeDocument,
+                docDragData.moveDocument,
+                docDragData.treeViewDoc === this.props.treeView.props.Document
+            );
+            e.stopPropagation();
+            !added && e.preventDefault();
+            return added;
         }
+        return false;
     };
 
     dropping: boolean = false;
@@ -924,7 +935,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                 PanelHeight={return18}
                 contextMenuItems={this.contextMenuItems}
                 renderDepth={1}
-                isContentActive={this.props.isContentActive}
+                isContentActive={emptyFunction} //this.props.isContentActive}
                 isDocumentActive={this.props.isContentActive}
                 focus={this.refocus}
                 whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index d7e073c5f..e6f8f3071 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -418,15 +418,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @undoBatch
     internalLinkDrop(e: Event, de: DragManager.DropEvent, linkDragData: DragManager.LinkDragData, xp: number, yp: number) {
         if (linkDragData.linkDragView.props.docViewPath().includes(this.props.docViewPath().lastElement())) {
-            // dragged document is a child of this collection
-            if (!linkDragData.linkDragView.props.CollectionFreeFormDocumentView?.() || linkDragData.dragDocument.embedContainer !== this.props.Document) {
-                // if the source doc view's embedContainer isn't this same freeformcollectionlinkDragData.dragDocument.embedContainer === this.props.Document
+            let added = false;
+            //  do nothing if link is dropped into any freeform view parent of dragged document
+            if (!linkDragData.dragDocument.embedContainer || linkDragData.dragDocument.embedContainer !== this.rootDoc) {
                 const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, x: xp, y: yp, title: 'dropped annotation' });
-                this.props.addDocument?.(source);
+                added = this.props.addDocument?.(source) ? true : false;
                 de.complete.linkDocument = DocUtils.MakeLink(linkDragData.linkSourceGetAnchor(), source, { link_relationship: 'annotated by:annotation of' }); // TODODO this is where in text links get passed
             }
-            e.stopPropagation(); //  do nothing if link is dropped into any freeform view parent of dragged document
-            return true;
+            e.stopPropagation();
+            !added && e.preventDefault();
+            return added;
         }
         return false;
     }
@@ -1297,8 +1298,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 childFilters={this.childDocFilters}
                 childFiltersByRanges={this.childDocRangeFilters}
                 searchFilterDocs={this.searchFilterDocs}
-                isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive}
-                isContentActive={this.props.childContentsActive ?? this.props.isContentActive() === false ? returnFalse : emptyFunction}
+                isDocumentActive={this.props.childDocumentsActive?.() || this.rootDoc._isGroup ? this.props.isDocumentActive : this.isContentActive}
+                isContentActive={this.props.childContentsActive ?? this.isContentActive() === false ? returnFalse : emptyFunction}
                 focus={this.Document._isGroup ? this.groupFocus : this.isAnnotationOverlay ? this.props.focus : this.focus}
                 addDocTab={this.addDocTab}
                 addDocument={this.props.addDocument}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 898452ccb..5febbe83e 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -385,6 +385,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         newCollection._width = this.Bounds.width;
         newCollection._height = this.Bounds.height;
         newCollection._isGroup = makeGroup;
+        newCollection._dragWhenActive = makeGroup;
         newCollection.forceActive = makeGroup;
         newCollection.x = this.Bounds.left;
         newCollection.y = this.Bounds.top;
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 32b42223b..6dcd2d422 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -220,6 +220,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
                         }
                     })
                 );
+                return true;
             }
         }
         return false;
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index fa3a9f352..4df5dad5a 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -220,6 +220,7 @@ export class CollectionMultirowView extends CollectionSubView() {
                         }
                     })
                 );
+                return true;
             }
         }
         return false;
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 06b227b4b..ee5bf82ed 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -434,7 +434,6 @@ export class CollectionSchemaView extends CollectionSubView() {
     @action
     onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
         if (de.complete.columnDragData) {
-            e.stopPropagation();
             const mouseX = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y)[0];
             const index = this.findDropIndex(mouseX);
             this.moveColumn(de.complete.columnDragData.colIndex, index ?? de.complete.columnDragData.colIndex);
@@ -448,6 +447,7 @@ export class CollectionSchemaView extends CollectionSubView() {
                 });
             });
 
+            e.stopPropagation();
             return true;
         }
         const draggedDocs = de.complete.docDragData?.draggedDocuments;
@@ -462,7 +462,6 @@ export class CollectionSchemaView extends CollectionSubView() {
                 if (draggedView) DocumentManager.Instance.RemoveView(draggedView);
                 DocumentManager.Instance.AddViewRenderedCb(doc, dv => dv.select(true));
             });
-            e.stopPropagation();
             return true;
         }
         return false;
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index d747c4527..2290e0711 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -42,13 +42,14 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
     };
 
     @undoBatch
-    private internalDrop = (event: Event, dropEvent: DragManager.DropEvent, fieldKey: string) => {
+    private internalDrop = (e: Event, dropEvent: DragManager.DropEvent, fieldKey: string) => {
         if (dropEvent.complete.docDragData) {
-            event.stopPropagation(); // prevent parent Doc from registering new position so that it snaps back into place
             const droppedDocs = dropEvent.complete.docDragData?.droppedDocuments;
-            const res = dropEvent.complete.docDragData.moveDocument?.(droppedDocs, this.rootDoc, (doc: Doc | Doc[]) => this.addDoc(doc instanceof Doc ? doc : doc.lastElement(), fieldKey));
-            res && (droppedDocs.lastElement().embedContainer = this.dataDoc);
-            return res;
+            const added = dropEvent.complete.docDragData.moveDocument?.(droppedDocs, this.rootDoc, (doc: Doc | Doc[]) => this.addDoc(doc instanceof Doc ? doc : doc.lastElement(), fieldKey));
+            droppedDocs.lastElement().embedContainer = this.dataDoc;
+            !added && e.preventDefault();
+            e.stopPropagation(); // prevent parent Doc from registering new position so that it snaps back into place
+            return added;
         }
     };
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index fbc84389d..b6f1626f8 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -356,9 +356,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     componentDidMount() {
         this.setupHandlers();
     }
-    preDropFunc = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => {
-        if (de.complete.docDragData && this.isContentActive()) {
-            targetAction && (de.complete.docDragData.dropAction = targetAction);
+    preDropFunc = (e: Event, de: DragManager.DropEvent) => {
+        const dropAction = this.layoutDoc.dropAction as dropActionType;
+        if (de.complete.docDragData && this.isContentActive() && !this.props.treeViewDoc) {
+            dropAction && (de.complete.docDragData.dropAction = dropAction);
             e.stopPropagation();
         }
     };
@@ -492,8 +493,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     }
                     preventDefault = false;
                 }
-
-                this._singleClickFunc = clickFunc ?? (() => this._componentView?.select?.(e.ctrlKey || e.metaKey, e.shiftKey) ?? this.props.select(e.ctrlKey || e.metaKey || e.shiftKey));
+                const sendToBack = e.altKey;
+                this._singleClickFunc =
+                    clickFunc ??
+                    (() =>
+                        sendToBack
+                            ? this.props.DocumentView().props.CollectionFreeFormDocumentView?.().props.bringToFront(this.rootDoc, true)
+                            : this._componentView?.select?.(e.ctrlKey || e.metaKey, e.shiftKey) ?? this.props.select(e.ctrlKey || e.metaKey || e.shiftKey));
                 const waitFordblclick = this.props.waitForDoubleClickToClick?.() ?? this.Document.waitForDoubleClickToClick;
                 if ((clickFunc && waitFordblclick !== 'never') || waitFordblclick === 'always') {
                     this._doubleClickTimeout && clearTimeout(this._doubleClickTimeout);
@@ -541,7 +547,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 //if (this.props.isSelected(true) && this.rootDoc.type !== DocumentType.PDF && this.layoutDoc._type_collection !== CollectionViewType.Docking) e.preventDefault();
 
                 // listen to move events if document content isn't active or document is draggable
-                if (!this.layoutDoc._lockedPosition && (!this.isContentActive() || this.props.dragAction || this.rootDoc.dragAction)) {
+                if (!this.layoutDoc._lockedPosition && (!this.isContentActive() || BoolCast(this.rootDoc._dragWhenActive))) {
                     document.addEventListener('pointermove', this.onPointerMove);
                 }
             }
@@ -617,15 +623,14 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         if (this.props.dontRegisterView || this.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return false;
         if (this.props.Document === Doc.ActiveDashboard) {
             e.stopPropagation();
-            alert((e.target as any)?.closest?.('*.lm_content') ? "You can't perform this move most likely because you don't have permission to modify the destination." : 'Linking to document tabs not yet supported. Drop link on document content.');
             e.preventDefault();
+            alert((e.target as any)?.closest?.('*.lm_content') ? "You can't perform this move most likely because you don't have permission to modify the destination." : 'Linking to document tabs not yet supported. Drop link on document content.');
             return true;
         }
         const linkdrag = de.complete.annoDragData ?? de.complete.linkDragData;
         if (linkdrag) {
             linkdrag.linkSourceDoc = linkdrag.linkSourceGetAnchor();
             if (linkdrag.linkSourceDoc) {
-                e.stopPropagation();
                 if (de.complete.annoDragData && !de.complete.annoDragData.dropDocument) {
                     de.complete.annoDragData.dropDocument = de.complete.annoDragData.dropDocCreator(undefined);
                 }
@@ -633,6 +638,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     const dropDoc = de.complete.annoDragData?.dropDocument ?? this._componentView?.getAnchor?.(true) ?? this.rootDoc;
                     de.complete.linkDocument = DocUtils.MakeLink(linkdrag.linkSourceDoc, dropDoc, {}, undefined, [de.x, de.y - 50]);
                 }
+                e.stopPropagation();
                 return true;
             }
         }
diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx
index 8a88bede6..61711417f 100644
--- a/src/client/views/nodes/FunctionPlotBox.tsx
+++ b/src/client/views/nodes/FunctionPlotBox.tsx
@@ -80,9 +80,10 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
     @undoBatch
     drop = (e: Event, de: DragManager.DropEvent) => {
         if (de.complete.docDragData?.droppedDocuments.length) {
+            const added = de.complete.docDragData.droppedDocuments.reduce((res, doc) => res && Doc.AddDocToList(this.dataDoc, this.props.fieldKey, doc), true);
+            !added && e.preventDefault();
             e.stopPropagation(); // prevent parent Doc from registering new position so that it snaps back into place
-            de.complete.docDragData.droppedDocuments.map(doc => Doc.AddDocToList(this.dataDoc, this.props.fieldKey, doc));
-            return false;
+            return added;
         }
         return false;
     };
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 64d6814df..f67930a3f 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -135,19 +135,17 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     @action
     drop = (e: Event, de: DragManager.DropEvent) => {
         if (de.complete.docDragData) {
+            let added = true;
             const targetIsBullseye = (ele: HTMLElement): boolean => {
                 if (!ele) return false;
                 if (ele === this._overlayIconRef.current) return true;
                 return targetIsBullseye(ele.parentElement as HTMLElement);
             };
             if (de.metaKey || targetIsBullseye(e.target as HTMLElement)) {
-                de.complete.docDragData.droppedDocuments.forEach(
-                    action((drop: Doc) => {
-                        Doc.AddDocToList(this.dataDoc, this.fieldKey + '-alternates', drop);
-                        this.rootDoc[this.fieldKey + '_usePath'] = 'alternate:hover';
-                        e.stopPropagation();
-                    })
-                );
+                added = de.complete.docDragData.droppedDocuments.reduce((last: boolean, drop: Doc) => {
+                    this.rootDoc[this.fieldKey + '_usePath'] = 'alternate:hover';
+                    return last && Doc.AddDocToList(this.dataDoc, this.fieldKey + '-alternates', drop);
+                }, true);
             } else if (de.altKey || !this.dataDoc[this.fieldKey]) {
                 const layoutDoc = de.complete.docDragData?.draggedDocuments[0];
                 const targetField = Doc.LayoutFieldKey(layoutDoc);
@@ -156,10 +154,13 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                     this.dataDoc[this.fieldKey] = ObjectField.MakeCopy(targetDoc[targetField] as ImageField);
                     Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(targetDoc), this.fieldKey);
                     Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(targetDoc), this.fieldKey);
-                    e.stopPropagation();
                 }
             }
+            !added && e.preventDefault();
+            e.stopPropagation();
+            return added;
         }
+        return false;
     };
 
     @undoBatch
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index 52f3575cb..4439be0cd 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -74,7 +74,9 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProp
         if (docDragData && missingParams?.includes((e.target as any).textContent)) {
             this.paramsDoc[(e.target as any).textContent] = new List<Doc>(docDragData.droppedDocuments.map((d, i) => (d.onDragStart ? docDragData.draggedDocuments[i] : d)));
             e.stopPropagation();
+            return true;
         }
+        return false;
     };
 
     @observable _mouseOver = false;
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
index 37fda14fc..3ad3c911d 100644
--- a/src/client/views/nodes/ScriptingBox.tsx
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -270,8 +270,12 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
     // sets field of the corresponding field key (param name) to be dropped document
     @action
     onDrop = (e: Event, de: DragManager.DropEvent, fieldKey: string) => {
-        Doc.SetInPlace(this.rootDoc, fieldKey, de.complete.docDragData?.droppedDocuments[0], true);
-        e.stopPropagation();
+        if (de.complete.docDragData) {
+            de.complete.docDragData.droppedDocuments.forEach(doc => Doc.SetInPlace(this.rootDoc, fieldKey, doc, true));
+            e.stopPropagation();
+            return true;
+        }
+        return false;
     };
 
     // deletes a param from all areas in which it is stored
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 2e8f6c696..da0fc9ffb 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -531,36 +531,50 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     @undoBatch
     @action
     drop = (e: Event, de: DragManager.DropEvent) => {
-        if (de.complete.annoDragData) de.complete.annoDragData.dropDocCreator = () => this.getAnchor(true);
+        if (de.complete.annoDragData) {
+            de.complete.annoDragData.dropDocCreator = () => this.getAnchor(true);
+            e.stopPropagation();
+            return true;
+        }
         const dragData = de.complete.docDragData;
         if (dragData) {
-            const draggedDoc = dragData.draggedDocuments.length && dragData.draggedDocuments[0];
-            // replace text contents whend dragging with Alt
-            if (draggedDoc && draggedDoc.type === DocumentType.RTF && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.altKey) {
-                if (draggedDoc.data instanceof RichTextField) {
-                    Doc.GetProto(this.dataDoc)[this.fieldKey] = new RichTextField(draggedDoc.data.Data, draggedDoc.data.Text);
-                    e.stopPropagation();
-                }
-                // embed document when dragg marked as embed
-            } else if (de.embedKey) {
-                const target = dragData.droppedDocuments[0];
-                const node = schema.nodes.dashDoc.create({
-                    width: target[Width](),
-                    height: target[Height](),
-                    title: 'dashDoc',
-                    docId: target[Id],
-                    float: 'unset',
-                });
-                if (!['embed', 'copy'].includes((dragData.dropAction ?? '') as any)) {
-                    dragData.removeDocument?.(dragData.draggedDocuments[0]);
+            const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc.proto), this.fieldKey) ? DocCast(this.layoutDoc.proto) : this.dataDoc;
+            const effectiveAcl = GetEffectiveAcl(dataDoc);
+            let added = [AclEdit, AclAdmin, AclSelfEdit].includes(effectiveAcl);
+            const draggedDoc = dragData.draggedDocuments.lastElement();
+            if (added) {
+                // replace text contents when dragging with Alt
+                if (de.altKey) {
+                    const fieldKey = Doc.LayoutFieldKey(draggedDoc);
+                    if (draggedDoc[fieldKey] instanceof RichTextField && !Doc.AreProtosEqual(draggedDoc, this.props.Document)) {
+                        Doc.GetProto(this.dataDoc)[this.fieldKey] = Field.Copy(draggedDoc[fieldKey]);
+                    }
+
+                    // embed document when drag marked as embed
+                } else if (de.embedKey) {
+                    const node = schema.nodes.dashDoc.create({
+                        width: draggedDoc[Width](),
+                        height: draggedDoc[Height](),
+                        title: 'dashDoc',
+                        docId: draggedDoc[Id],
+                        float: 'unset',
+                    });
+                    if (!['embed', 'copy'].includes((dragData.dropAction ?? '') as any)) {
+                        added = dragData.removeDocument?.(draggedDoc) ? true : false;
+                    }
+                    if (added) {
+                        draggedDoc._freeform_fitContentsToBox = true;
+                        draggedDoc.embedContainer = this.rootDoc;
+                        const view = this._editorView!;
+                        view.dispatch(view.state.tr.insert(view.posAtCoords({ left: de.x, top: de.y })!.pos, node));
+                    }
                 }
-                target._freeform_fitContentsToBox = true;
-                target.embedContainer = this.rootDoc;
-                const view = this._editorView!;
-                view.dispatch(view.state.tr.insert(view.posAtCoords({ left: de.x, top: de.y })!.pos, node));
-                e.stopPropagation();
             } // otherwise, fall through to outer collection to handle drop
+            !added && e.preventDefault();
+            e.stopPropagation();
+            return added;
         }
+        return false;
     };
 
     getNodeEndpoints(context: Node, node: Node): { from: number; to: number } | null {
diff --git a/src/fields/Proxy.ts b/src/fields/Proxy.ts
index 55d1d9ea4..c076f5fe1 100644
--- a/src/fields/Proxy.ts
+++ b/src/fields/Proxy.ts
@@ -1,5 +1,5 @@
 import { Deserializable } from '../client/util/SerializationHelper';
-import { FieldWaiting, Opt } from './Doc';
+import { Field, FieldWaiting, Opt } from './Doc';
 import { primitive, serializable } from 'serializr';
 import { observable, action, runInAction, computed } from 'mobx';
 import { DocServer } from '../client/DocServer';
@@ -39,7 +39,7 @@ export class ProxyField<T extends RefField> extends ObjectField {
     }
 
     [ToScriptString]() {
-        return 'invalid';
+        return Field.toScriptString(this[ToValue](undefined)?.value); // not sure this is quite right since it doesn't recreate a proxy field, but better than 'invalid' ?
     }
     [ToString]() {
         return 'ProxyField';
diff --git a/src/fields/util.ts b/src/fields/util.ts
index e3d1abc53..e439768ee 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -433,7 +433,8 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any
                                   lastValue = ObjectField.MakeCopy(receiver[prop]);
                               },
                               prop: 'assign list',
-                          }
+                          },
+                    diff?.items
                 );
         }
         target[Update](op);
-- 
cgit v1.2.3-70-g09d2


From 8c3527d61dc5c0191da81a54c3efd0a14bdc8886 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 29 Jun 2023 22:31:53 -0400
Subject: fixed minimap panning.  fixed initial zooming on images.

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

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

diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 97d4d989b..0dcf17b48 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -569,7 +569,7 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
         if (!this.renderBounds) return null;
         const miniWidth = (this.props.PanelWidth() / NumCast(this.props.document._freeform_scale, 1) / this.renderBounds.dim) * 100;
         const miniHeight = (this.props.PanelHeight() / NumCast(this.props.document._freeform_scale, 1) / this.renderBounds.dim) * 100;
-        const miniLeft = 50 + ((NumCast(this.props.document._freeform_) - this.renderBounds.cx) / this.renderBounds.dim) * 100 - miniWidth / 2;
+        const miniLeft = 50 + ((NumCast(this.props.document._freeform_panX) - this.renderBounds.cx) / this.renderBounds.dim) * 100 - miniWidth / 2;
         const miniTop = 50 + ((NumCast(this.props.document._freeform_panY) - this.renderBounds.cy) / this.renderBounds.dim) * 100 - miniHeight / 2;
         const miniSize = this.returnMiniSize();
         return this.props.hideMinimap() ? null : (
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index e6f8f3071..ef396c6b3 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1053,7 +1053,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         if (this.layoutDoc._Transform || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return;
         e.stopPropagation();
         const docHeight = NumCast(this.rootDoc[Doc.LayoutFieldKey(this.rootDoc) + '_nativeHeight'], this.nativeHeight);
-        const scrollable = NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this.props.PanelHeight() / this.nativeDimScaling;
+        const scrollable = NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this.props.PanelHeight() / this.nativeDimScaling + 1e-4;
         switch (!e.ctrlKey ? Doc.UserDoc().freeformScrollMode : freeformScrollMode.Pan) {
             case freeformScrollMode.Pan:
                 // if ctrl is selected then zoom
-- 
cgit v1.2.3-70-g09d2


From c0d45c06df10d29cae5f46eeecb220a11588c3a1 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 30 Jun 2023 23:43:59 -0400
Subject: fixed onBrowseClick for notetaking. fixed panning when in onBrowse
 explore mode.   fixed switching dashboards to not display an empty stack.

---
 src/client/util/DocumentManager.ts                 | 23 +++++++++++------
 src/client/views/DashboardView.tsx                 |  3 ++-
 .../views/collections/CollectionDockingView.tsx    |  1 +
 .../views/collections/CollectionNoteTakingView.tsx |  3 ++-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 30 ++++++++++------------
 src/client/views/nodes/DocumentView.tsx            |  5 ++--
 .../views/nodes/formattedText/FormattedTextBox.tsx |  2 +-
 7 files changed, 38 insertions(+), 29 deletions(-)

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 7e3302067..351f9ef7c 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -228,7 +228,7 @@ export class DocumentManager {
     public showDocumentView = async (targetDocView: DocumentView, options: DocFocusOptions) => {
         const docViewPath = targetDocView.docViewPath.slice();
         let rootContextView = docViewPath.shift();
-        await (rootContextView && this.focusViewsInPath(rootContextView, options, async () => ({ childDocView: docViewPath.shift(), viewSpec: undefined })));
+        await (rootContextView && this.focusViewsInPath(rootContextView, options, async () => ({ childDocView: docViewPath.shift(), viewSpec: undefined, focused: false })));
         if (options.toggleTarget && (!options.didMove || targetDocView.rootDoc.hidden)) targetDocView.rootDoc.hidden = !targetDocView.rootDoc.hidden;
         else if (options.openLocation?.startsWith(OpenWhere.toggle) && !options.didMove && rootContextView) DocumentViewInternal.addDocTabFunc(rootContextView.rootDoc, options.openLocation);
     };
@@ -242,7 +242,7 @@ export class DocumentManager {
     public showDocument = async (
         targetDoc: Doc, // document to display
         options: DocFocusOptions, // options for how to navigate to target
-        finished?: () => void
+        finished?: (changed: boolean) => void // func called after focusing on target with flag indicating whether anything needed to be done.
     ) => {
         const docContextPath = DocumentManager.GetContextPath(targetDoc, true);
         if (docContextPath.some(doc => doc.hidden)) options.toggleTarget = false;
@@ -269,22 +269,29 @@ export class DocumentManager {
         docContextPath.shift();
         const childViewIterator = async (docView: DocumentView) => {
             const innerDoc = docContextPath.shift();
-            return { viewSpec: innerDoc, childDocView: innerDoc && !innerDoc.layout_unrendered ? (await docView.ComponentView?.getView?.(innerDoc)) ?? this.getDocumentView(innerDoc) : undefined };
+            return { focused: false, viewSpec: innerDoc, childDocView: innerDoc && !innerDoc.layout_unrendered ? (await docView.ComponentView?.getView?.(innerDoc)) ?? this.getDocumentView(innerDoc) : undefined };
         };
+
         if (rootContextView) {
             const target = await this.focusViewsInPath(rootContextView, options, childViewIterator);
             this.restoreDocView(target.viewSpec, target.docView, options, target.contextView ?? target.docView, targetDoc);
-        }
-        finished?.();
+            finished?.(target.focused);
+        } else finished?.(false);
     };
 
-    focusViewsInPath = async (docView: DocumentView, options: DocFocusOptions, iterator: (docView: DocumentView) => Promise<{ viewSpec: Opt<Doc>; childDocView: Opt<DocumentView> }>) => {
+    focusViewsInPath = async (
+        docView: DocumentView, //
+        options: DocFocusOptions,
+        iterator: (docView: DocumentView) => Promise<{ viewSpec: Opt<Doc>; childDocView: Opt<DocumentView>; focused: boolean }>
+    ) => {
         let contextView: DocumentView | undefined; // view containing context that contains target
+        let focused = false;
         while (true) {
             docView.rootDoc.layout_fieldKey === 'layout_icon' ? await new Promise<void>(res => docView.iconify(res)) : undefined;
-            docView.props.focus(docView.rootDoc, options); // focus the view within its container
+            const nextFocus = docView.props.focus(docView.rootDoc, options); // focus the view within its container
+            focused = focused || (nextFocus === undefined ? false : true);
             const { childDocView, viewSpec } = await iterator(docView);
-            if (!childDocView) return { viewSpec: options.anchorDoc ?? viewSpec ?? docView.rootDoc, docView, contextView };
+            if (!childDocView) return { viewSpec: options.anchorDoc ?? viewSpec ?? docView.rootDoc, docView, contextView, focused };
             contextView = docView;
             docView = childDocView;
         }
diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx
index 6aae302ac..f6d843201 100644
--- a/src/client/views/DashboardView.tsx
+++ b/src/client/views/DashboardView.tsx
@@ -347,7 +347,8 @@ export class DashboardView extends React.Component {
                 },
             ],
         };
-        Doc.SetInPlace(dashboard, 'dockingConfig', JSON.stringify(reset), true);
+        if (dashboard.dockingConfig && dashboard.dockingConfig !== Doc.GetProto(dashboard).dockingConfig) dashboard.dockingConfig = JSON.stringify(reset);
+        else Doc.SetInPlace(dashboard, 'dockingConfig', JSON.stringify(reset), true);
         return reset;
     };
 
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index a5d7e7864..3bf3f1a74 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -359,6 +359,7 @@ export class CollectionDockingView extends CollectionSubView() {
         } catch (e) {}
         this._goldenLayout?.destroy();
         window.removeEventListener('resize', this.onResize);
+        window.removeEventListener('mouseup', this.onPointerUp);
 
         this._reactionDisposer?.();
         this._lightboxReactionDisposer?.();
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index f1c4c2cdf..fb995301a 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -192,7 +192,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
         if (found) {
             const top = found.getBoundingClientRect().top;
             const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top);
-            if (Math.floor(localTop[1]) !== 0) {
+            if (Math.floor(localTop[1]) !== 0 && Math.ceil(this.props.PanelHeight()) < (this._mainCont?.scrollHeight || 0)) {
                 let focusSpeed = options.zoomTime ?? 500;
                 smoothScroll(focusSpeed, this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc);
                 return focusSpeed;
@@ -252,6 +252,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                 layout_showTitle={this.props.childlayout_showTitle}
                 dragAction={StrCast(this.layoutDoc.childDragAction) as dropActionType}
                 onClick={this.onChildClickHandler}
+                onBrowseClick={this.props.onBrowseClick}
                 onDoubleClick={this.onChildDoubleClickHandler}
                 ScreenToLocalTransform={noteTakingDocTransform}
                 focus={this.focusDocument}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index ef396c6b3..ba31916a7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -2256,22 +2256,20 @@ class CollectionFreeFormBackgroundGrid extends React.Component<CollectionFreeFor
 export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY: number) {
     const browseTransitionTime = 500;
     SelectionManager.DeselectAll();
-    if (
-        dv.props.focus(dv.props.Document, {
-            willZoomCentered: true,
-            zoomScale: 0.8,
-            zoomTime: browseTransitionTime,
-        }) === undefined
-    ) {
-        const selfFfview = !dv.rootDoc._isGroup && dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined;
-        let parFfview = dv.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
-        while (parFfview?.rootDoc._isGroup) parFfview = parFfview.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
-        const ffview = selfFfview && selfFfview.rootDoc[selfFfview.scaleFieldKey] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it.  Otherwise, zoom out on the parent ffview
-        ffview?.zoomSmoothlyAboutPt(ffview.getTransform().transformPoint(clientX, clientY), 0.5, browseTransitionTime);
-        Doc.linkFollowHighlight(dv?.props.Document, false);
-    } else {
-        DocumentManager.Instance.showDocument(dv.rootDoc, { zoomScale: 0.8, willZoomCentered: true });
-    }
+    DocumentManager.Instance.showDocument(dv.rootDoc, { zoomScale: 0.8, willZoomCentered: true }, (focused: boolean) => {
+        if (!focused) {
+            const selfFfview = !dv.rootDoc._isGroup && dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined;
+            let containers = dv.props.docViewPath();
+            let parFfview = dv.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
+            for (var cont of containers) {
+                parFfview = parFfview ?? cont.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
+            }
+            while (parFfview?.rootDoc._isGroup) parFfview = parFfview.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
+            const ffview = selfFfview && selfFfview.rootDoc[selfFfview.scaleFieldKey] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it.  Otherwise, zoom out on the parent ffview
+            ffview?.zoomSmoothlyAboutPt(ffview.getTransform().transformPoint(clientX, clientY), 0.5, browseTransitionTime);
+            Doc.linkFollowHighlight(dv?.props.Document, false);
+        }
+    });
 }
 ScriptingGlobals.add(CollectionBrowseClick);
 ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index b6f1626f8..733ee1ed1 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -900,8 +900,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     };
     childFilters = () => [...this.props.childFilters(), ...StrListCast(this.layoutDoc.childFilters)];
 
-    /// disable pointer events on content when there's an enabled onClick script, or if contents are marked inactive
-    contentPointerEvents = () => ((!this.disableClickScriptFunc && this.onClickHandler) || this.isContentActive() === false ? 'none' : this.pointerEvents);
+    /// disable pointer events on content when there's an enabled onClick script (but not the browse script), or if contents are marked inactive
+    contentPointerEvents = () => ((!this.disableClickScriptFunc && this.onClickHandler && !this.props.onBrowseClick?.()) || this.isContentActive() === false ? 'none' : this.pointerEvents);
+
     @computed get contents() {
         TraceMobx();
         const isInk = StrCast(this.layoutDoc.layout).includes(InkingStroke.name) && !this.props.LayoutTemplateString;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index da0fc9ffb..026864ddd 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -2079,7 +2079,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     style={{
                         overflow: this.layout_autoHeight && this.props.CollectionFreeFormDocumentView?.() ? 'hidden' : undefined, //x this breaks viewing an layout_autoHeight doc in its own tab, or in the lightbox
                         height: this.props.height || (this.layout_autoHeight && this.props.renderDepth && !this.props.suppressSetHeight ? 'max-content' : undefined),
-                        pointerEvents: Doc.ActiveTool === InkTool.None ? undefined : 'none',
+                        pointerEvents: Doc.ActiveTool === InkTool.None && !this.props.onBrowseClick ? undefined : 'none',
                     }}
                     onContextMenu={this.specificContextMenu}
                     onKeyDown={this.onKeyDown}
-- 
cgit v1.2.3-70-g09d2


From 08f125f5880247280c02633feeb31a8df1912b97 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 31 Jul 2023 00:06:16 -0400
Subject: fixed dictation into text boxes to stop when component unmounts.  
 fixed some icons.  fixed multiToggle for alignments.  added link docs to user
 cache.  fixed background color for tab stack buttons.    added a bunch of
 @computeds to try to help performacne with lots of docs.  chnaged text boxes
 to no expand/contract padding when selected.

---
 package-lock.json                                  | 103 ++-------------------
 package.json                                       |   2 +-
 src/client/DocServer.ts                            |   3 +-
 src/client/documents/Documents.ts                  |   4 +-
 src/client/util/CurrentUserUtils.ts                |  58 ++++++------
 src/client/util/RTFMarkup.tsx                      |   2 +-
 src/client/views/PropertiesButtons.tsx             |   4 +-
 .../views/collections/CollectionDockingView.scss   |   1 +
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  20 ++--
 .../collectionLinear/CollectionLinearView.tsx      |  33 +++----
 .../collectionSchema/CollectionSchemaView.tsx      |   1 -
 src/client/views/global/globalScripts.ts           |  52 +++++------
 src/client/views/nodes/DocumentView.tsx            |  55 ++++++-----
 src/client/views/nodes/FontIconBox/FontIconBox.tsx |  72 +++++++-------
 .../nodes/formattedText/FormattedTextBox.scss      |  20 +---
 .../views/nodes/formattedText/FormattedTextBox.tsx |  48 ++++++----
 src/fields/Doc.ts                                  |   2 +-
 src/fields/List.ts                                 |  46 ++++-----
 18 files changed, 228 insertions(+), 298 deletions(-)

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

diff --git a/package-lock.json b/package-lock.json
index 54d1b568c..082531ea0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6351,9 +6351,9 @@
             }
         },
         "browndash-components": {
-            "version": "0.0.90",
-            "resolved": "https://registry.npmjs.org/browndash-components/-/browndash-components-0.0.90.tgz",
-            "integrity": "sha512-56dsp2yoP5axTnXSPtSpWsQUkcj3hAekYBHLk7Kw6OV7LHVuu5PZruSmGE9xDrdhAFz2UdUyxKeAGB3PdbQoew==",
+            "version": "0.0.91",
+            "resolved": "https://registry.npmjs.org/browndash-components/-/browndash-components-0.0.91.tgz",
+            "integrity": "sha512-VYW1C1XY6CcQD4OceQHK/2VGhSa0H0iboom7M9zy5F6WHJP03LFHrwKkRtgsqwxQBQLsj69XXlEfygbSkV3FvQ==",
             "requires": {
                 "@emotion/react": "^11.11.1",
                 "@emotion/styled": "^11.11.0",
@@ -6902,97 +6902,12 @@
                                         "strip-ansi": "^7.0.1"
                                     }
                                 },
-                                "string-width-cjs": {
-                                    "version": "npm:string-width@4.2.3",
-                                    "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
-                                    "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
-                                    "requires": {
-                                        "emoji-regex": "^8.0.0",
-                                        "is-fullwidth-code-point": "^3.0.0",
-                                        "strip-ansi": "^6.0.1"
-                                    },
-                                    "dependencies": {
-                                        "ansi-regex": {
-                                            "version": "5.0.1",
-                                            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-                                            "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
-                                        },
-                                        "emoji-regex": {
-                                            "version": "8.0.0",
-                                            "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
-                                            "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
-                                        },
-                                        "strip-ansi": {
-                                            "version": "6.0.1",
-                                            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-                                            "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-                                            "requires": {
-                                                "ansi-regex": "^5.0.1"
-                                            }
-                                        }
-                                    }
-                                },
                                 "strip-ansi": {
                                     "version": "7.1.0",
                                     "bundled": true,
                                     "requires": {
                                         "ansi-regex": "^6.0.1"
                                     }
-                                },
-                                "strip-ansi-cjs": {
-                                    "version": "npm:strip-ansi@6.0.1",
-                                    "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-                                    "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-                                    "requires": {
-                                        "ansi-regex": "^5.0.1"
-                                    },
-                                    "dependencies": {
-                                        "ansi-regex": {
-                                            "version": "5.0.1",
-                                            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-                                            "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
-                                        }
-                                    }
-                                },
-                                "wrap-ansi-cjs": {
-                                    "version": "npm:wrap-ansi@7.0.0",
-                                    "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
-                                    "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
-                                    "requires": {
-                                        "ansi-styles": "^4.0.0",
-                                        "string-width": "^4.1.0",
-                                        "strip-ansi": "^6.0.0"
-                                    },
-                                    "dependencies": {
-                                        "ansi-regex": {
-                                            "version": "5.0.1",
-                                            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-                                            "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
-                                        },
-                                        "emoji-regex": {
-                                            "version": "8.0.0",
-                                            "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
-                                            "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
-                                        },
-                                        "string-width": {
-                                            "version": "4.2.3",
-                                            "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
-                                            "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
-                                            "requires": {
-                                                "emoji-regex": "^8.0.0",
-                                                "is-fullwidth-code-point": "^3.0.0",
-                                                "strip-ansi": "^6.0.1"
-                                            }
-                                        },
-                                        "strip-ansi": {
-                                            "version": "6.0.1",
-                                            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-                                            "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-                                            "requires": {
-                                                "ansi-regex": "^5.0.1"
-                                            }
-                                        }
-                                    }
                                 }
                             }
                         },
@@ -8497,7 +8412,7 @@
                             }
                         },
                         "string-width-cjs": {
-                            "version": "npm:string-width-cjs@4.2.3",
+                            "version": "npm:string-width@4.2.3",
                             "bundled": true,
                             "requires": {
                                 "emoji-regex": "^8.0.0",
@@ -8520,7 +8435,7 @@
                             }
                         },
                         "strip-ansi-cjs": {
-                            "version": "npm:strip-ansi-cjs@6.0.1",
+                            "version": "npm:strip-ansi@6.0.1",
                             "bundled": true,
                             "requires": {
                                 "ansi-regex": "^5.0.1"
@@ -8679,7 +8594,7 @@
                             }
                         },
                         "wrap-ansi-cjs": {
-                            "version": "npm:wrap-ansi-cjs@7.0.0",
+                            "version": "npm:wrap-ansi@7.0.0",
                             "bundled": true,
                             "requires": {
                                 "ansi-styles": "^4.0.0",
@@ -9865,9 +9780,9 @@
             "integrity": "sha512-GiZn9D4Z/rSYvTeg1ljAIsEqFm0LaN9gVtwDCrKL80zHtS31p9BAjmTxVqTQDMpwlMolJZOFntUG2uwyj7DAqw=="
         },
         "core-js-compat": {
-            "version": "3.31.1",
-            "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.31.1.tgz",
-            "integrity": "sha512-wIDWd2s5/5aJSdpOJHfSibxNODxoGoWOBHt8JSPB41NOE94M7kuTPZCYLOlTtuoXTsBPKobpJ6T+y0SSy5L9SA==",
+            "version": "3.32.0",
+            "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.32.0.tgz",
+            "integrity": "sha512-7a9a3D1k4UCVKnLhrgALyFcP7YCsLOQIxPd0dKjf/6GuPcgyiGP70ewWdCGrSK7evyhymi0qO4EqCmSJofDeYw==",
             "requires": {
                 "browserslist": "^4.21.9"
             }
diff --git a/package.json b/package.json
index 8da591254..822460a10 100644
--- a/package.json
+++ b/package.json
@@ -177,7 +177,7 @@
         "body-parser": "^1.19.2",
         "bootstrap": "^4.6.1",
         "brotli": "^1.3.3",
-        "browndash-components": "^0.0.90",
+        "browndash-components": "^0.0.91",
         "browser-assert": "^1.2.1",
         "bson": "^4.6.1",
         "canvas": "^2.9.3",
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index ba59a9f50..ad5a73598 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -49,7 +49,8 @@ export namespace DocServer {
         }
         const filtered = Array.from(Object.keys(_cache)).filter(key => {
             const doc = _cache[key] as Doc;
-            if (!(StrCast(doc.author).includes('.edu') || StrCast(doc.author).includes('.com')) || doc.author == Doc.CurrentUserEmail) return true;
+            return true;
+            if (!(StrCast(doc.author).includes('.edu') || StrCast(doc.author).includes('.com')) || doc.author === Doc.CurrentUserEmail) return true;
             return false;
         });
         if (filtered.length === lastCacheUpdate) return;
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index f22fa9f17..d38e2292c 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -636,7 +636,7 @@ export namespace Docs {
                 DocumentType.CONFIG,
                 {
                     layout: { view: CollectionView, dataField: defaultDataKey },
-                    options: { layout_hideLinkButton: true, pointerEvents: 'none', layout_unrendered: true },
+                    options: { layout_hideLinkButton: true, layout_unrendered: true },
                 },
             ],
             [
@@ -854,7 +854,7 @@ export namespace Docs {
             dataProps.author_date = new DateField();
             if (fieldKey) {
                 dataProps[`${fieldKey}_modificationDate`] = new DateField();
-                dataProps[fieldKey] = data;
+                dataProps[fieldKey] = options.data ?? data;
 
                 // so that the list of annotations is already initialised, prevents issues in addonly.
                 // without this, if a doc has no annotations but the user has AddOnly privileges, they won't be able to add an annotation because they would have needed to create the field's list which they don't have permissions to do.
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index a86011042..8e91bf83f 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -586,6 +586,11 @@ export class CurrentUserUtils {
         dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }),
         _lockedPosition: true, isSystem: true, flexDirection: "row"
     })
+    static multiToggleList = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.FontIconDocument({
+        ...opts, data:docs, _gridGap: 0, _xMargin: 5, _yMargin: 5, layout_boxShadow: "0 0", _forceActive: true,
+        dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }),
+        _lockedPosition: true, isSystem: true, flexDirection: "row"
+    })
 
     static createToolButton = (opts: DocumentOptions) => Docs.Create.FontIconDocument({
         btnType: ButtonType.ToolButton, _forceActive: true, _layout_hideContextMenu: true, 
@@ -640,17 +645,17 @@ export class CurrentUserUtils {
               btnList: new List<string>(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]) },
             { title: "Font Size",toolTip: "Font size (%size)",    btnType: ButtonType.NumberDropdownButton,              toolType:"fontSize", ignoreClick: true,   scripts: {script:  '{ return setFontAttr(self.toolType, value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 6 },
             { title: "Color",    toolTip: "Font color (%color)",  btnType: ButtonType.ColorButton,  icon: "font",        toolType:"fontColor",ignoreClick: true,   scripts: {script:  '{ return setFontAttr(self.toolType, value, _readOnly_);}'}},
-            { title: "Highlight",toolTip:"Font highlight",        btnType: ButtonType.ColorButton,  icon: "highlighter", toolType:"highlight",ignoreClick: true,   scripts: {script:  '{ return setFontAttr(self.toolType, value, _readOnly_);}'},funcs: {hidden: "IsNoviceMode()"} },
+            { title: "Highlight",toolTip: "Font highlight",       btnType: ButtonType.ColorButton,  icon: "highlighter", toolType:"highlight",ignoreClick: true,   scripts: {script:  '{ return setFontAttr(self.toolType, value, _readOnly_);}'},funcs: {hidden: "IsNoviceMode()"} },
             { title: "Bold",     toolTip: "Bold (Ctrl+B)",        btnType: ButtonType.ToggleButton, icon: "bold",        toolType:"bold",     ignoreClick: true,   scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
             { title: "Italic",   toolTip: "Italic (Ctrl+I)",      btnType: ButtonType.ToggleButton, icon: "italic",      toolType:"italics",  ignoreClick: true,   scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
             { title: "Under",    toolTip: "Underline (Ctrl+U)",   btnType: ButtonType.ToggleButton, icon: "underline",   toolType:"underline",ignoreClick: true,   scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
             { title: "Bullets",  toolTip: "Bullet List",          btnType: ButtonType.ToggleButton, icon: "list",        toolType:"bullet",   ignoreClick: true,   scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
             { title: "#",        toolTip: "Number List",          btnType: ButtonType.ToggleButton, icon: "list-ol",     toolType:"decimal",  ignoreClick: true,   scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
-            { title: "Alignment",toolTip: "Alignment",            btnType: ButtonType.MultiToggleButton,                  toolType:"alignment", ignoreClick: true,
+            { title: "Align",    toolTip: "Alignment",            btnType: ButtonType.MultiToggleButton,                 toolType:"alignment", ignoreClick: true,
               subMenu: [
-                { title: "Left",     toolTip: "Left align (Cmd-[)",   btnType: ButtonType.ToggleButton, icon: "align-left",  toolType:"left",  ignoreClick: true,      scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}' }},
-                { title: "Center",   toolTip: "Center align (Cmd-\\)",btnType: ButtonType.ToggleButton, icon: "align-center",toolType:"center",ignoreClick: true,      scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
-                { title: "Right",    toolTip: "Right align (Cmd-])",  btnType: ButtonType.ToggleButton, icon: "align-right", toolType:"right", ignoreClick: true,      scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
+                { title: "Left",     toolTip: "Left align (Cmd-[)",   btnType: ButtonType.ToggleButton, icon: "align-left",  toolType:"left",  ignoreClick: true,  scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}' }},
+                { title: "Center",   toolTip: "Center align (Cmd-\\)",btnType: ButtonType.ToggleButton, icon: "align-center",toolType:"center",ignoreClick: true,  scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
+            { title: "Right",    toolTip: "Right align (Cmd-])",  btnType: ButtonType.ToggleButton, icon: "align-right", toolType:"right", ignoreClick: true,  scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
               ]
             },
             { title: "Dictate",  toolTip: "Dictate",              btnType: ButtonType.ToggleButton, icon: "microphone",  toolType:"dictation", ignoreClick: true,  scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'}},
@@ -730,32 +735,31 @@ export class CurrentUserUtils {
         return DocUtils.AssignScripts(DocUtils.AssignOpts(btnDoc, reqdOpts) ?? Docs.Create.FontIconDocument(reqdOpts), params.scripts, reqdFuncs);
     }
 
+    static setupContextMenuBtn(params:Button, menuDoc:Doc):Doc {
+        const menuBtnDoc = DocListCast(menuDoc?.data).find(doc => doc.title === params.title);
+        if (!params.subMenu) { // button does not have a sub menu
+            return this.setupContextMenuButton(params, menuBtnDoc);
+        }  
+        // linear view
+        const reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, undoIgnoreFields: new List<string>(['width', "linearView_IsOpen"]),
+            childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: params.scripts?.onClick ? false : true,
+            linearView_SubMenu: true, linearView_Expandable: params.btnType !== ButtonType.MultiToggleButton};
+        
+        const items = !menuBtnDoc ? [] : params.subMenu?.map(sub => this.setupContextMenuBtn(sub, menuBtnDoc) );
+        if (params.btnType === ButtonType.MultiToggleButton) {
+            const list = DocUtils.AssignScripts( DocUtils.AssignDocField(menuDoc, StrCast(params.title), 
+                (opts) => this.multiToggleList(opts, items??[]), reqdSubMenuOpts, items), params.scripts);
+            return list;
+        }
+        return DocUtils.AssignScripts(
+            DocUtils.AssignDocField(menuDoc, StrCast(params.title), (opts) => this.linearButtonList(opts, items??[]), reqdSubMenuOpts, items), params.scripts, params.funcs);
+    }
+
     /// Initializes all the default buttons for the top bar context menu
     static setupContextMenuButtons(doc: Doc, field="myContextMenuBtns") {
         const reqdCtxtOpts:DocumentOptions = { title: "context menu buttons", undoIgnoreFields:new List<string>(['width', "linearView_IsOpen"]), flexGap: 0, childDragAction: 'embed', childDontRegisterViews: true,  linearView_IsOpen: true, ignoreClick: true, linearView_Expandable: false, _height: 35 };
         const ctxtMenuBtnsDoc = DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), reqdCtxtOpts, undefined);
-        const ctxtMenuBtns = CurrentUserUtils.contextMenuTools().map(params => {
-            const menuBtnDoc = DocListCast(ctxtMenuBtnsDoc?.data).find(doc => doc.title === params.title);
-            if (!params.subMenu) { // button does not have a sub menu
-                return this.setupContextMenuButton(params, menuBtnDoc);
-            } else { // linear view
-                let reqdSubMenuOpts;
-                if (params.btnType === ButtonType.MultiToggleButton) {
-                    reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, undoIgnoreFields: new List<string>(['width', "linearView_IsOpen"]),
-                    childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: params.scripts?.onClick ? false : true,
-                    linearView_SubMenu: true, linearView_Dropdown: true, };
-                } else {
-                    reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, undoIgnoreFields: new List<string>(['width', "linearView_IsOpen"]),
-                    childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: params.scripts?.onClick ? false : true,
-                    linearView_SubMenu: true, linearView_Expandable: true, };
-                }
-                const items = params.subMenu?.map(sub =>
-                    this.setupContextMenuButton(sub, DocListCast(menuBtnDoc?.data).find(doc => doc.title === sub.title))
-                );
-                return DocUtils.AssignScripts(
-                    DocUtils.AssignDocField(ctxtMenuBtnsDoc, StrCast(params.title), (opts) => this.linearButtonList(opts, items??[]), reqdSubMenuOpts, items), params.scripts, params.funcs);
-            }
-        });
+        const ctxtMenuBtns = CurrentUserUtils.contextMenuTools().map(params => this.setupContextMenuBtn(params, ctxtMenuBtnsDoc) );
         return DocUtils.AssignOpts(ctxtMenuBtnsDoc, reqdCtxtOpts, ctxtMenuBtns);
     }
 
diff --git a/src/client/util/RTFMarkup.tsx b/src/client/util/RTFMarkup.tsx
index b93d4f293..c2f121e1f 100644
--- a/src/client/util/RTFMarkup.tsx
+++ b/src/client/util/RTFMarkup.tsx
@@ -30,7 +30,7 @@ export class RTFMarkup extends React.Component<{}> {
      */
     @computed get cheatSheet() {
         return (
-            <div style={{ textAlign: 'initial', height: '100%' }}>
+            <div style={{ background: 'white', textAlign: 'initial', height: '100%' }}>
                 <p>
                     <b style={{ fontSize: 'larger' }}>{`wiki:phrase`}</b>
                     {` display wikipedia page for entered text (terminate with carriage return)`}
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 2e3668268..b74eabcc3 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -244,10 +244,10 @@ export class PropertiesButtons extends React.Component<{}, {}> {
     @computed get layout_autoHeightButton() {
         // store previous dimensions to store old values
         return this.propertyToggleBtn(
-            on => 'Auto\xA0Size',
+            on => (on ? 'AUTO\xA0SIZE' : 'FIXED SIZE'),
             '_layout_autoHeight',
             on => `Automatical vertical sizing to show all content`,
-            on => 'arrows-alt-v'
+            on => <FontAwesomeIcon icon="arrows-alt-v" size="lg" />
         );
     }
 
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss
index a4c5229aa..d93015506 100644
--- a/src/client/views/collections/CollectionDockingView.scss
+++ b/src/client/views/collections/CollectionDockingView.scss
@@ -176,6 +176,7 @@
         display: flex;
         align-content: center;
         justify-content: center;
+        background: $dark-gray;
     }
 
     .lm_controls > li {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index ba31916a7..b5e9994dd 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1264,13 +1264,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             return this.addDocument?.(newDoc);
         }
     };
-    pointerEvents = () => {
+    @computed get _pointerEvents() {
         const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
         const pointerEvents = DocumentDecorations.Instance.Interacting
             ? 'none'
             : this.props.childPointerEvents ?? (this.props.viewDefDivClick || (engine === computePassLayout.name && !this.props.isSelected(true)) || this.isContentActive() === false ? 'none' : this.props.pointerEvents?.());
         return pointerEvents;
-    };
+    }
+    pointerEvents = () => this._pointerEvents;
+    childContentsActive = () => (this.props.childContentsActive ?? this.isContentActive() === false ? returnFalse : emptyFunction)();
     getChildDocView(entry: PoolData) {
         const childLayout = entry.pair.layout;
         const childData = entry.pair.data;
@@ -1299,7 +1301,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 childFiltersByRanges={this.childDocRangeFilters}
                 searchFilterDocs={this.searchFilterDocs}
                 isDocumentActive={this.props.childDocumentsActive?.() || this.rootDoc._isGroup ? this.props.isDocumentActive : this.isContentActive}
-                isContentActive={this.props.childContentsActive ?? this.isContentActive() === false ? returnFalse : emptyFunction}
+                isContentActive={this.childContentsActive}
                 focus={this.Document._isGroup ? this.groupFocus : this.isAnnotationOverlay ? this.props.focus : this.focus}
                 addDocTab={this.addDocTab}
                 addDocument={this.props.addDocument}
@@ -1435,21 +1437,17 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         }.bind(this)
     );
 
-    childPositionProviderUnmemoized = (doc: Doc, replica: string) => {
-        return this._layoutPoolData.get(doc[Id] + (replica || ''));
-    };
+    childPositionProviderUnmemoized = (doc: Doc, replica: string) => this._layoutPoolData.get(doc[Id] + (replica || ''));
     childDataProvider = computedFn(
         function childDataProvider(this: any, doc: Doc, replica: string) {
-            return this._layoutPoolData.get(doc[Id] + (replica || ''));
+            return this.childPositionProviderUnmemoized(doc, replica);
         }.bind(this)
     );
 
-    childSizeProviderUnmemoized = (doc: Doc, replica: string) => {
-        return this._layoutSizeData.get(doc[Id] + (replica || ''));
-    };
+    childSizeProviderUnmemoized = (doc: Doc, replica: string) => this._layoutSizeData.get(doc[Id] + (replica || ''));
     childSizeProvider = computedFn(
         function childSizeProvider(this: any, doc: Doc, replica: string) {
-            return this._layoutSizeData.get(doc[Id] + (replica || ''));
+            return this.childSizeProviderUnmemoized(doc, replica);
         }.bind(this)
     );
 
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index 56b8366d0..2254b2e5f 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -204,7 +204,7 @@ export class CollectionLinearView extends CollectionSubView() {
 
         const menuOpener = (
             <Toggle
-                text={Cast(this.props.Document.icon, 'string', null)} 
+                text={Cast(this.props.Document.icon, 'string', null)}
                 icon={Cast(this.props.Document.icon, 'string', null) ? undefined : <FontAwesomeIcon icon={isExpanded ? 'minus' : 'plus'} />}
                 type={Type.TERT}
                 color={StrCast(Doc.UserDoc().userVariantColor, Colors.MEDIUM_BLUE)}
@@ -212,7 +212,7 @@ export class CollectionLinearView extends CollectionSubView() {
                 toggleType={ToggleType.BUTTON}
                 toggleStatus={BoolCast(this.layoutDoc.linearView_IsOpen)}
                 onClick={() => {
-                        this.layoutDoc.linearView_IsOpen = !isExpanded;
+                    this.layoutDoc.linearView_IsOpen = !isExpanded;
                 }}
                 tooltip={isExpanded ? 'Close' : 'Open'}
                 fillWidth={true}
@@ -223,25 +223,22 @@ 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" ref={this.createDashEventsTarget} onContextMenu={this.myContextMenu} style={{ minHeight: this.dimension(), pointerEvents: 'all' }}>
-                    {this.props.Document.linearView_Dropdown ? 
-                        <div>Hello World!</div>
-                        : 
+                    {
                         <>
-                        {!this.props.Document.linearView_Expandable ? null : menuOpener}
-                        {!this.layoutDoc.linearView_IsOpen ? null : (
-                            <div
-                                className="collectionLinearView-content"
-                                style={{
-                                    height: this.dimension(),
-                                    flexDirection: flexDir as any,
-                                    gap: flexGap,
-                                }}>
-                                {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))}
-                            </div>
-                        )}
+                            {menuOpener}
+                            {!this.layoutDoc.linearView_IsOpen ? null : (
+                                <div
+                                    className="collectionLinearView-content"
+                                    style={{
+                                        height: this.dimension(),
+                                        flexDirection: flexDir as any,
+                                        gap: flexGap,
+                                    }}>
+                                    {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))}
+                                </div>
+                            )}
                         </>
                     }
-                    
                 </div>
             </div>
         );
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index ee5bf82ed..babe5c810 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -810,7 +810,6 @@ export class CollectionSchemaView extends CollectionSubView() {
     }
 
     @computed get sortedDocs() {
-        trace();
         const field = StrCast(this.layoutDoc.sortField);
         const desc = BoolCast(this.layoutDoc.sortDesc);
         const docs = !field
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index b906065a0..61920cdef 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -1,26 +1,28 @@
-import { Colors } from "browndash-components";
-import { runInAction, action } from "mobx";
-import { aggregateBounds } from "../../../Utils";
-import { Doc } from "../../../fields/Doc";
-import { Width, Height } from "../../../fields/DocSymbols";
-import { InkTool } from "../../../fields/InkField";
-import { Cast, StrCast, NumCast, BoolCast } from "../../../fields/Types";
-import { WebField } from "../../../fields/URLField";
-import { GestureUtils } from "../../../pen-gestures/GestureUtils";
-import { LinkManager } from "../../util/LinkManager";
-import { ScriptingGlobals } from "../../util/ScriptingGlobals";
-import { SelectionManager } from "../../util/SelectionManager";
-import { UndoManager } from "../../util/UndoManager";
-import { GestureOverlay } from "../GestureOverlay";
-import { InkTranscription } from "../InkTranscription";
-import { ActiveFillColor, SetActiveFillColor, ActiveIsInkMask, SetActiveIsInkMask, ActiveInkWidth, SetActiveInkWidth, ActiveInkColor, SetActiveInkColor } from "../InkingStroke";
-import { CollectionFreeFormView } from "../collections/collectionFreeForm";
-import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
-import { WebBox } from "../nodes/WebBox";
-import { RichTextMenu } from "../nodes/formattedText/RichTextMenu";
-import { DocumentType } from "../../documents/DocumentTypes";
+import { Colors } from 'browndash-components';
+import { runInAction, action } from 'mobx';
+import { aggregateBounds } from '../../../Utils';
+import { Doc } from '../../../fields/Doc';
+import { Width, Height } from '../../../fields/DocSymbols';
+import { InkTool } from '../../../fields/InkField';
+import { Cast, StrCast, NumCast, BoolCast } from '../../../fields/Types';
+import { WebField } from '../../../fields/URLField';
+import { GestureUtils } from '../../../pen-gestures/GestureUtils';
+import { LinkManager } from '../../util/LinkManager';
+import { ScriptingGlobals } from '../../util/ScriptingGlobals';
+import { SelectionManager } from '../../util/SelectionManager';
+import { undoable, UndoManager } from '../../util/UndoManager';
+import { GestureOverlay } from '../GestureOverlay';
+import { InkTranscription } from '../InkTranscription';
+import { ActiveFillColor, SetActiveFillColor, ActiveIsInkMask, SetActiveIsInkMask, ActiveInkWidth, SetActiveInkWidth, ActiveInkColor, SetActiveInkColor } from '../InkingStroke';
+import { CollectionFreeFormView } from '../collections/collectionFreeForm';
+import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
+import { WebBox } from '../nodes/WebBox';
+import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
+import { DocumentType } from '../../documents/DocumentTypes';
 
-ScriptingGlobals.add(function IsNoneSelected() { return SelectionManager.Views().length <= 0; }, "are no document selected");
+ScriptingGlobals.add(function IsNoneSelected() {
+    return SelectionManager.Views().length <= 0;
+}, 'are no document selected');
 
 // toggle: Set overlay status of selected document
 ScriptingGlobals.add(function setView(view: string) {
@@ -52,7 +54,7 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b
             if (contentFrameNumber !== undefined) {
                 CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.rootDoc, { fieldKey: color });
             } else {
-                console.log('setting color to: ', color)
+                console.log('setting color to: ', color);
                 dv.rootDoc['_' + fieldKey] = color;
             }
         });
@@ -119,7 +121,6 @@ ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'grid' | 'snapli
    ]);
 
     if (checkResult) {
-        console.log(attr, map.get(attr)?.checkResult(selected))
         return map.get(attr)?.checkResult(selected);
     }
     const batch = map.get(attr)?.waitForRender ? UndoManager.StartBatch('set freeform attribute') : { end: () => {} };
@@ -193,10 +194,9 @@ ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?:
 
     const map = new Map(attrs.concat(alignments).concat(listings));
     if (checkResult) {
-        console.log(charStyle, checkResult, map.get(charStyle)?.checkResult());
         return map.get(charStyle)?.checkResult();
     }
-    map.get(charStyle)?.toggle();
+    undoable(() => map.get(charStyle)?.toggle(), 'toggle ' + charStyle)();
 });
 
 export function checkInksToGroup() {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 2990e2159..70d2f95ea 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,5 +1,5 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from 'mobx';
 import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
 import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal';
@@ -314,7 +314,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     @computed get titleHeight() {
         return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.TitleHeight) || 0;
     }
-    get pointerEvents(): 'none' | 'all' | 'visiblePainted' | undefined {
+    @computed get pointerEvents(): 'none' | 'all' | 'visiblePainted' | undefined {
         return this.props.styleProvider?.(this.Document, this.props, StyleProp.PointerEvents + (this.props.isSelected() ? ':selected' : ''));
     }
     @computed get finalLayoutKey() {
@@ -862,28 +862,32 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15);
     };
 
-    rootSelected = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || (this.props.Document.rootDocument && this.props.rootSelected?.(outsideReaction)) || false;
+    @computed get _rootSelected() {
+        return this.props.isSelected(false) || (this.props.Document.rootDocument && this.props.rootSelected?.(false)) || false;
+    }
+    rootSelected = (outsideReaction?: boolean) => this._rootSelected;
     panelHeight = () => this.props.PanelHeight() - this.headerMargin;
     screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin);
     onClickFunc: any = () => (this.disableClickScriptFunc ? undefined : this.onClickHandler);
     setHeight = (height: number) => (this.layoutDoc._height = height);
     setContentView = action((view: { getAnchor?: (addAsAnnotation: boolean) => Doc; forward?: () => boolean; back?: () => boolean }) => (this._componentView = view));
-    isContentActive = (outsideReaction?: boolean): boolean | undefined => {
+    @computed get _isContentActive() {
         //  true  - if the document has been activated directly or indirectly (by having its children selected)
         //  false - if its pointer events are explicitly turned off or if it's container tells it that it's inactive
         // undefined - it is not active, but it should be responsive to actions that might active it or its contents (eg clicking)
-        return this.props.isContentActive() === false || this.props.pointerEvents?.() === 'none' || (this.rootDoc.pointerEvents === 'none' && !StrCast(this.props.LayoutTemplateString).includes(KeyValueBox.name))
+        return this.props.isContentActive() === false || this.props.pointerEvents?.() === 'none'
             ? false
             : Doc.ActiveTool !== InkTool.None || SnappingManager.GetIsDragging() || this.rootSelected() || this.rootDoc.forceActive || this._componentView?.isAnyChildContentActive?.() || this.props.isContentActive()
             ? true
             : undefined;
-    };
+    }
+    isContentActive = (): boolean | undefined => this._isContentActive;
     @observable _retryThumb = 1;
-    thumbShown = () => {
-        const childHighlighted = () =>
-            Array.from(Doc.highlightedDocs.keys())
-                .concat(Array.from(Doc.brushManager.BrushedDoc.keys()))
-                .some(doc => Doc.AreProtosEqual(DocCast(doc.annotationOn), this.rootDoc));
+    @computed get _thumbShown() {
+        const childHighlighted = () => false;
+        // Array.from(Doc.highlightedDocs.keys())
+        //     .concat(Array.from(Doc.brushManager.BrushedDoc.keys()))
+        //     .some(doc => Doc.AreProtosEqual(DocCast(doc.annotationOn), this.rootDoc));
         const childOverlayed = () => Array.from(DocumentManager._overlayViews).some(view => Doc.AreProtosEqual(view.rootDoc, this.rootDoc));
         return !this.props.LayoutTemplateString &&
             !this.isContentActive() &&
@@ -893,12 +897,15 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             ((!childHighlighted() && !childOverlayed() && !Doc.isBrushedHighlightedDegree(this.rootDoc)) || this.rootDoc._type_collection === CollectionViewType.Docking)
             ? true
             : false;
-    };
+    }
+    thumbShown = () => this._thumbShown;
     childFilters = () => [...this.props.childFilters(), ...StrListCast(this.layoutDoc.childFilters)];
 
     /// disable pointer events on content when there's an enabled onClick script (but not the browse script) and the contents aren't forced active, or if contents are marked inactive
-    contentPointerEvents = () => ((!this.disableClickScriptFunc && this.onClickHandler && !this.props.onBrowseClick?.() && this.isContentActive() !== true) || this.isContentActive() === false ? 'none' : this.pointerEvents);
-
+    @computed get _contentPointerEvents() {
+        return (!this.disableClickScriptFunc && this.onClickHandler && !this.props.onBrowseClick?.() && this.isContentActive() !== true) || this.isContentActive() === false ? 'none' : this.pointerEvents;
+    }
+    contentPointerEvents = () => this._contentPointerEvents;
     @computed get contents() {
         TraceMobx();
         const isInk = StrCast(this.layoutDoc.layout).includes(InkingStroke.name) && !this.props.LayoutTemplateString;
@@ -1081,7 +1088,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             <div
                 className="documentView-captionWrapper"
                 style={{
-                    pointerEvents: this.Document.ignoreClick ? 'none' : this.isContentActive() || this.props.isDocumentActive?.() ? 'all' : undefined,
+                    pointerEvents: this.rootDoc.ignoreClick ? 'none' : this.isContentActive() || this.props.isDocumentActive?.() ? 'all' : undefined,
                     minWidth: 50 * ffscale(),
                     maxHeight: `max(100%, ${20 * ffscale()}px)`,
                 }}>
@@ -1216,16 +1223,22 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             case PresEffect.Lightspeed: return <LightSpeed {...effectProps}>{renderDoc}</LightSpeed>;
         }
     }
+    @computed get highlighting() {
+        return this.props.styleProvider?.(this.props.Document, this.props, StyleProp.Highlighting);
+    }
+    @computed get borderPath() {
+        return this.props.styleProvider?.(this.props.Document, this.props, StyleProp.BorderPath);
+    }
     render() {
         TraceMobx();
-        const highlighting = this.props.styleProvider?.(this.props.Document, this.props, StyleProp.Highlighting);
-        const borderPath = this.props.styleProvider?.(this.props.Document, this.props, StyleProp.BorderPath);
+        const highlighting = this.highlighting;
+        const borderPath = this.borderPath;
         const boxShadow =
             this.props.treeViewDoc || !highlighting
                 ? this.boxShadow
                 : highlighting && this.borderRounding && highlighting.highlightStyle !== 'dashed'
                 ? `0 0 0 ${highlighting.highlightIndex}px ${highlighting.highlightColor}`
-                : this.boxShadow || (this.props.Document.isTemplateForField ? 'black 0.2vw 0.2vw 0.8vw' : undefined);
+                : this.boxShadow || (this.rootDoc.isTemplateForField ? 'black 0.2vw 0.2vw 0.8vw' : undefined);
         const renderDoc = this.renderDoc({
             borderRadius: this.borderRounding,
             outline: highlighting && !this.borderRounding && !highlighting.highlightStroke ? `${highlighting.highlightColor} ${highlighting.highlightStyle} ${highlighting.highlightIndex}px` : 'solid 0px',
@@ -1241,9 +1254,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 onContextMenu={this.onContextMenu}
                 onPointerDown={this.onPointerDown}
                 onClick={this.onClick}
-                onPointerEnter={e => (!SnappingManager.GetIsDragging() || DragManager.CanEmbed) && Doc.BrushDoc(this.props.Document)}
-                onPointerOver={e => (!SnappingManager.GetIsDragging() || DragManager.CanEmbed) && Doc.BrushDoc(this.props.Document)}
-                onPointerLeave={e => !isParentOf(this.ContentDiv, document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y)) && Doc.UnBrushDoc(this.props.Document)}
+                onPointerEnter={e => (!SnappingManager.GetIsDragging() || DragManager.CanEmbed) && Doc.BrushDoc(this.rootDoc)}
+                onPointerOver={e => (!SnappingManager.GetIsDragging() || DragManager.CanEmbed) && Doc.BrushDoc(this.rootDoc)}
+                onPointerLeave={e => !isParentOf(this.ContentDiv, document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y)) && Doc.UnBrushDoc(this.rootDoc)}
                 style={{
                     borderRadius: this.borderRounding,
                     pointerEvents: this.pointerEvents === 'visiblePainted' ? 'none' : this.pointerEvents,
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index da1b89200..ad3532502 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -1,6 +1,6 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Button, ColorPicker, Dropdown, DropdownType, EditableText, IconButton, IListItemProps, NumberDropdown, NumberDropdownType, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components';
+import { Button, MultiToggle, ColorPicker, Dropdown, DropdownType, EditableText, IconButton, IListItemProps, NumberDropdown, NumberDropdownType, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components';
 import { action, computed, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -21,6 +21,7 @@ import { RichTextMenu } from '../formattedText/RichTextMenu';
 import './FontIconBox.scss';
 import { SelectedDocView } from '../../selectedDoc';
 import { Utils } from '../../../../Utils';
+import { FaAlignCenter, FaAlignJustify, FaAlignLeft, FaAlignRight } from 'react-icons/fa';
 
 export enum ButtonType {
     TextButton = 'textBtn',
@@ -209,13 +210,10 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
             if (isViewDropdown) {
                 const selectedDocs: Doc[] = SelectionManager.Docs();
                 const selected = SelectionManager.Docs().lastElement();
-                console.log('selected');
                 if (selected) {
                     if (StrCast(selected.type) === DocumentType.COL) {
                         text = StrCast(selected._type_collection);
-                        console.log('collection selected', text);
                     } else {
-                        console.log('doc selected', selected.title);
                         dropdown = false;
                         if (selectedDocs.length > 1) {
                             text = selectedDocs.length + ' selected';
@@ -240,8 +238,6 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
             console.log(e);
         }
 
-        console.log('current item: ', text);
-
         // Get items to place into the list
         const list: IListItemProps[] = this.buttonList
             .filter(value => !Doc.noviceMode || !noviceList.length || noviceList.includes(value))
@@ -295,6 +291,34 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
             />
         );
     }
+    @computed get multiToggleButton() {
+        // Determine the type of toggle button
+        const tooltip: string = StrCast(this.rootDoc.toolTip);
+
+        const script = ScriptCast(this.rootDoc.onClick);
+        const toggleStatus = script ? script.script.run({ this: this.layoutDoc, self: this.rootDoc, value: undefined, _readOnly_: true }).result : false;
+        // Colors
+        const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
+        const items = DocListCast(this.rootDoc.data);
+        return (
+            <MultiToggle
+                tooltip={`Toggle ${tooltip}`}
+                type={Type.PRIM}
+                color={color}
+                label={this.label}
+                items={DocListCast(this.rootDoc.data).map(item => ({
+                    icon: <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={StrCast(item.icon) as any} color={color} />,
+                    tooltip: StrCast(item.toolTip),
+                    val: StrCast(item.toolType),
+                }))}
+                selectedVal={StrCast(items.find(itemDoc => ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, self: itemDoc, value: undefined, _readOnly_: true }).result)?.toolType)}
+                setSelectedVal={(val: string | number) => {
+                    const itemDoc = items.find(item => item.toolType === val);
+                    itemDoc && ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, self: itemDoc, value: val, _readOnly_: false });
+                }}
+            />
+        );
+    }
 
     @computed get toggleButton() {
         // Determine the type of toggle button
@@ -356,48 +380,34 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
     render() {
         // determine dash button metadata
         const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
-        const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
         const tooltip: string = StrCast(this.rootDoc.toolTip);
         const onClickScript = ScriptCast(this.rootDoc.onClick);
-        const script = ScriptCast(this.rootDoc.script);
         // TODO:glr Add label of button type
         let button: JSX.Element = this.defaultButton;
         // prettier-ignore
         switch (this.type) {
             case ButtonType.EditableText: 
-                button = this.editableText;
-                break;
+                button = this.editableText; break;
             case ButtonType.DropdownList:
-                button = this.dropdownListButton;
-                break;
+                button = this.dropdownListButton; break;
             case ButtonType.ColorButton: 
-                button = this.colorButton; 
-                break;
+                button = this.colorButton;  break;
             case ButtonType.NumberDropdownButton: 
             case ButtonType.NumberInlineButton:
             case ButtonType.NumberSliderButton: 
-                button = this.numberDropdown; 
-                break;
+                button = this.numberDropdown;  break;
             case ButtonType.DropdownButton: 
-                button = this.dropdownButton; 
-                break;
+                button = this.dropdownButton;  break;
+            case ButtonType.MultiToggleButton: 
+                button = this.multiToggleButton; break; 
             case ButtonType.ToggleButton: button = this.toggleButton; break;
             case ButtonType.ClickButton:
             case ButtonType.ToolButton: 
-                button = (
-                    <IconButton tooltip={tooltip} 
-                    color={color} icon={this.Icon(color)!} label={this.label}/>
-                );
-                break;
+                button = <IconButton tooltip={tooltip} color={color} icon={this.Icon(color)!} label={this.label}/>; break;
             case ButtonType.TextButton: 
-                button = (
-                    <Button tooltip={tooltip} icon={this.Icon(color)!} text={StrCast(this.rootDoc.buttonText)} label={this.label}/>
-                );
-                break;
-            case ButtonType.MenuButton: button = (
-                    <IconButton tooltip={tooltip} onPointerDown={() => onClickScript?.script.run({ this: this.layoutDoc, self: this.rootDoc, _readOnly_: false })} tooltipPlacement='right' size={Size.LARGE} color={color} icon={this.Icon(color)!} label={this.label}/>
-                );
-                break;
+                button = <Button tooltip={tooltip} icon={this.Icon(color)!} text={StrCast(this.rootDoc.buttonText)} label={this.label}/>; break;
+            case ButtonType.MenuButton: 
+                button = <IconButton tooltip={tooltip} onPointerDown={() => onClickScript?.script.run({ this: this.layoutDoc, self: this.rootDoc, _readOnly_: false })} tooltipPlacement='right' size={Size.LARGE} color={color} icon={this.Icon(color)!} label={this.label}/>; break;
         }
 
         return button;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 109b62e6f..348bdd79e 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -84,7 +84,6 @@ audiotag:hover {
     height: 11;
 }
 
-.formattedTextBox-outer-selected,
 .formattedTextBox-outer {
     position: relative;
     overflow: auto;
@@ -92,9 +91,6 @@ audiotag:hover {
     width: 100%;
     height: unset;
 }
-.formattedTextBox-outer-selected {
-    cursor: text;
-}
 
 .formattedTextBox-sidebar-handle {
     position: absolute;
@@ -148,10 +144,8 @@ audiotag:hover {
 }
 
 .formattedTextBox-inner-rounded,
-.formattedTextBox-inner-rounded-selected,
 .formattedTextBox-inner,
-.formattedTextBox-inner-minimal,
-.formattedTextBox-inner-selected {
+.formattedTextBox-inner-minimal {
     height: 100%;
     white-space: pre-wrap;
     .ProseMirror:hover {
@@ -169,17 +163,6 @@ audiotag:hover {
         border-width: 1px;
     }
 }
-.formattedTextBox-inner-rounded-selected,
-.formattedTextBox-inner-selected {
-    > .ProseMirror {
-        padding: 10px;
-    }
-}
-.formattedTextBox-outer-selected {
-    > .ProseMirror:hover {
-        background: unset;
-    }
-}
 
 .gpt-typing-wrapper {
     padding: 10px;
@@ -640,7 +623,6 @@ footnote::before {
         }
     }
 
-    .formattedTextBox-outer-selected,
     .formattedTextBox-outer {
         position: relative;
         overflow: auto;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index a0eb328a1..24d2f0e13 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1557,7 +1557,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         );
     };
 
+    @action
     componentWillUnmount() {
+        if (this._recording) {
+            this._recording = !this._recording;
+        }
         Object.values(this._disposers).forEach(disposer => disposer?.());
         this.endUndoTypingBatch();
         FormattedTextBox.LiveTextUndo?.end();
@@ -1596,8 +1600,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         }
         if (this._recording && !e.ctrlKey && e.button === 0) {
             this.breakupDictation();
-            e.preventDefault();
-            e.stopPropagation();
         }
         this._downX = e.clientX;
         this._downY = e.clientY;
@@ -2043,17 +2045,25 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         }
     };
     _oldWheel: any;
+    @computed get fontColor() {
+        return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color);
+    }
+    @computed get fontSize() {
+        return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontSize);
+    }
+    @computed get fontFamily() {
+        return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontFamily);
+    }
+    @computed get fontWeight() {
+        return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontWeight);
+    }
     render() {
         TraceMobx();
-        const active = this.props.isContentActive();
         const scale = (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1);
         const rounded = StrCast(this.layoutDoc.layout_borderRounding) === '100%' ? '-rounded' : '';
-        if (!active && FormattedTextBoxComment.textBox === this) setTimeout(FormattedTextBoxComment.Hide);
-        const minimal = this.props.ignoreAutoHeight;
+        setTimeout(() => !this.props.isContentActive() && FormattedTextBoxComment.textBox === this && FormattedTextBoxComment.Hide);
         const paddingX = NumCast(this.layoutDoc._xMargin, this.props.xPadding || 0);
         const paddingY = NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0);
-        const selPad = (active && !this.layoutDoc._createDocOnCR) || minimal ? Math.min(paddingY, Math.min(paddingX, 10)) : 0;
-        const selPaddingClass = active && !this.layoutDoc._createDocOnCR && paddingY >= 10 ? '-selected' : '';
         const styleFromLayoutString = Doc.styleFromLayoutString(this.rootDoc, this.layoutDoc, this.props, scale); // this converts any expressions in the format string to style props.  e.g., <FormattedTextBox height='{this._headerHeight}px' >
         return styleFromLayoutString?.height === '0px' ? null : (
             <div
@@ -2074,19 +2084,20 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                               width: `${100 / scale}%`,
                               height: `${100 / scale}%`,
                           }),
-                    display: !this.props.isContentActive() && this.props.thumbShown?.() ? 'none' : undefined,
+                    // display: !this.props.isContentActive() && this.props.thumbShown?.() ? 'none' : undefined,
                     transition: 'inherit',
                     // overflowY: this.layoutDoc._layout_autoHeight ? "hidden" : undefined,
-                    color: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color),
-                    fontSize: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontSize),
-                    fontFamily: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontFamily),
-                    fontWeight: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontWeight),
+                    color: this.fontColor,
+                    fontSize: this.fontSize,
+                    fontFamily: this.fontFamily,
+                    fontWeight: this.fontWeight,
                     ...styleFromLayoutString,
                 }}>
                 <div
                     className="formattedTextBox-cont"
                     ref={this._ref}
                     style={{
+                        cursor: this.props.isContentActive() ? 'text' : undefined,
                         overflow: this.layout_autoHeight && this.props.CollectionFreeFormDocumentView?.() ? 'hidden' : undefined, //x this breaks viewing an layout_autoHeight doc in its own tab, or in the lightbox
                         height: this.props.height || (this.layout_autoHeight && this.props.renderDepth && !this.props.suppressSetHeight ? 'max-content' : undefined),
                         pointerEvents: Doc.ActiveTool === InkTool.None && !this.props.onBrowseClick?.() ? undefined : 'none',
@@ -2101,7 +2112,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     onPointerDown={this.onPointerDown}
                     onDoubleClick={this.onDoubleClick}>
                     <div
-                        className={`formattedTextBox-outer${active ? '-selected' : ''}`}
+                        className={`formattedTextBox-outer`}
                         ref={this._scrollRef}
                         style={{
                             width: this.props.dontSelectOnLoad ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`,
@@ -2110,15 +2121,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                         onScroll={this.onScroll}
                         onDrop={this.ondrop}>
                         <div
-                            className={minimal ? 'formattedTextBox-minimal' : `formattedTextBox-inner${rounded}${selPaddingClass}`}
+                            className={`formattedTextBox-inner${rounded}`}
                             ref={this.createDropTarget}
                             style={{
                                 padding: StrCast(this.layoutDoc._textBoxPadding),
-                                paddingLeft: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX - selPad}px`),
-                                paddingRight: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX - selPad}px`),
-                                paddingTop: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY - selPad}px`),
-                                paddingBottom: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY - selPad}px`),
-                                // pointerEvents: !active && IsFollowLinkScript(this.layoutDoc.onClick) ? 'none' : undefined,
+                                paddingLeft: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX}px`),
+                                paddingRight: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX}px`),
+                                paddingTop: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY}px`),
+                                paddingBottom: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY}px`),
                             }}
                         />
                     </div>
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 5a8a6e4b6..ad557e079 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -573,7 +573,7 @@ export namespace Doc {
 
     // compare whether documents or their protos match
     export function AreProtosEqual(doc?: Doc, other?: Doc) {
-        return doc && other && Doc.GetProto(doc) === Doc.GetProto(other);
+        return doc && other && (doc === other || Doc.GetProto(doc) === Doc.GetProto(other));
     }
 
     // Gets the data document for the document.  Note: this is mis-named -- it does not specifically
diff --git a/src/fields/List.ts b/src/fields/List.ts
index 183d644d3..f3fcc87f7 100644
--- a/src/fields/List.ts
+++ b/src/fields/List.ts
@@ -1,4 +1,4 @@
-import { action, observable } from 'mobx';
+import { action, computed, observable } from 'mobx';
 import { alias, list, serializable } from 'serializr';
 import { DocServer } from '../client/DocServer';
 import { ScriptingGlobals } from '../client/util/ScriptingGlobals';
@@ -72,13 +72,13 @@ class ListImpl<T extends Field> extends ObjectField {
             return res;
         },
         sort(cmpFunc: any) {
-            this[Self].__realFields(); // coerce retrieving entire array
+            this[Self].__realFields; // coerce retrieving entire array
             const res = this[Self].__fieldTuples.sort(cmpFunc ? (first: any, second: any) => cmpFunc(toRealField(first), toRealField(second)) : undefined);
             this[SelfProxy][FieldChanged]?.();
             return res;
         },
         splice: action(function (this: any, start: number, deleteCount: number, ...items: any[]) {
-            this[Self].__realFields(); // coerce retrieving entire array
+            this[Self].__realFields; // coerce retrieving entire array
             items = items.map(toObjectField);
             const list = this[Self];
             const removed = list.__fieldTuples.filter((item: any, i: number) => i >= start && i < start + deleteCount);
@@ -125,104 +125,104 @@ class ListImpl<T extends Field> extends ObjectField {
         },
         /// Accessor methods
         concat: action(function (this: any, ...items: any[]) {
-            this[Self].__realFields();
+            this[Self].__realFields;
             return this[Self].__fieldTuples.map(toRealField).concat(...items);
         }),
         includes(valueToFind: any, fromIndex: number) {
             if (valueToFind instanceof RefField) {
-                return this[Self].__realFields().includes(valueToFind, fromIndex);
+                return this[Self].__realFields.includes(valueToFind, fromIndex);
             } else {
                 return this[Self].__fieldTuples.includes(valueToFind, fromIndex);
             }
         },
         indexOf(valueToFind: any, fromIndex: number) {
             if (valueToFind instanceof RefField) {
-                return this[Self].__realFields().indexOf(valueToFind, fromIndex);
+                return this[Self].__realFields.indexOf(valueToFind, fromIndex);
             }
             return this[Self].__fieldTuples.indexOf(valueToFind, fromIndex);
         },
         join(separator: any) {
-            this[Self].__realFields();
+            this[Self].__realFields;
             return this[Self].__fieldTuples.map(toRealField).join(separator);
         },
         lastElement() {
-            return this[Self].__realFields().lastElement();
+            return this[Self].__realFields.lastElement();
         },
         lastIndexOf(valueToFind: any, fromIndex: number) {
             if (valueToFind instanceof RefField) {
-                return this[Self].__realFields().lastIndexOf(valueToFind, fromIndex);
+                return this[Self].__realFields.lastIndexOf(valueToFind, fromIndex);
             } else {
                 return this[Self].__fieldTuples.lastIndexOf(valueToFind, fromIndex);
             }
         },
         slice(begin: number, end: number) {
-            this[Self].__realFields();
+            this[Self].__realFields;
             return this[Self].__fieldTuples.slice(begin, end).map(toRealField);
         },
 
         /// Iteration methods
         entries() {
-            return this[Self].__realFields().entries();
+            return this[Self].__realFields.entries();
         },
         every(callback: any, thisArg: any) {
-            return this[Self].__realFields().every(callback, thisArg);
+            return this[Self].__realFields.every(callback, thisArg);
             // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
             // If we don't want to support the array parameter, we should use this version instead
             // return this[Self].__fieldTuples.every((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
         },
         filter(callback: any, thisArg: any) {
-            return this[Self].__realFields().filter(callback, thisArg);
+            return this[Self].__realFields.filter(callback, thisArg);
             // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
             // If we don't want to support the array parameter, we should use this version instead
             // return this[Self].__fieldTuples.filter((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
         },
         find(callback: any, thisArg: any) {
-            return this[Self].__realFields().find(callback, thisArg);
+            return this[Self].__realFields.find(callback, thisArg);
             // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
             // If we don't want to support the array parameter, we should use this version instead
             // return this[Self].__fieldTuples.find((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
         },
         findIndex(callback: any, thisArg: any) {
-            return this[Self].__realFields().findIndex(callback, thisArg);
+            return this[Self].__realFields.findIndex(callback, thisArg);
             // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
             // If we don't want to support the array parameter, we should use this version instead
             // return this[Self].__fieldTuples.findIndex((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
         },
         forEach(callback: any, thisArg: any) {
-            return this[Self].__realFields().forEach(callback, thisArg);
+            return this[Self].__realFields.forEach(callback, thisArg);
             // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
             // If we don't want to support the array parameter, we should use this version instead
             // return this[Self].__fieldTuples.forEach((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
         },
         map(callback: any, thisArg: any) {
-            return this[Self].__realFields().map(callback, thisArg);
+            return this[Self].__realFields.map(callback, thisArg);
             // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
             // If we don't want to support the array parameter, we should use this version instead
             // return this[Self].__fieldTuples.map((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
         },
         reduce(callback: any, initialValue: any) {
-            return this[Self].__realFields().reduce(callback, initialValue);
+            return this[Self].__realFields.reduce(callback, initialValue);
             // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
             // If we don't want to support the array parameter, we should use this version instead
             // return this[Self].__fieldTuples.reduce((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue);
         },
         reduceRight(callback: any, initialValue: any) {
-            return this[Self].__realFields().reduceRight(callback, initialValue);
+            return this[Self].__realFields.reduceRight(callback, initialValue);
             // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
             // If we don't want to support the array parameter, we should use this version instead
             // return this[Self].__fieldTuples.reduceRight((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue);
         },
         some(callback: any, thisArg: any) {
-            return this[Self].__realFields().some(callback, thisArg);
+            return this[Self].__realFields.some(callback, thisArg);
             // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
             // If we don't want to support the array parameter, we should use this version instead
             // return this[Self].__fieldTuples.some((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
         },
         values() {
-            return this[Self].__realFields().values();
+            return this[Self].__realFields.values();
         },
         [Symbol.iterator]() {
-            return this[Self].__realFields().values();
+            return this[Self].__realFields.values();
         },
     };
     static listGetter(target: any, prop: string | symbol, receiver: any): any {
@@ -262,7 +262,7 @@ class ListImpl<T extends Field> extends ObjectField {
 
     // this requests all ProxyFields at the same time to avoid the overhead
     // of separate network requests and separate updates to the React dom.
-    private __realFields() {
+    @computed private get __realFields() {
         const unrequested = this[FieldTuples].filter(f => f instanceof ProxyField && f.needsRequesting).map(f => f as ProxyField<RefField>);
         // if we find any ProxyFields that don't have a current value, then
         // start the server request for all of them
-- 
cgit v1.2.3-70-g09d2


From 2cea22bea6b335820b1030ca75ebeca4364b6769 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 10 Aug 2023 11:31:05 -0400
Subject: added client caching of jpg's in addition to png's.   modified
 jitterRotation to _rotation_jitter and changed where it is used.

---
 src/client/documents/Documents.ts                                 | 8 ++++++--
 src/client/views/StyleProvider.tsx                                | 4 ----
 src/client/views/collections/CollectionPileView.tsx               | 3 ++-
 .../collections/collectionFreeForm/CollectionFreeFormView.tsx     | 8 ++++++--
 src/server/server_Initialization.ts                               | 2 +-
 5 files changed, 15 insertions(+), 10 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index fccad80ee..8a4a82e6d 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1076,7 +1076,12 @@ export namespace Docs {
         }
 
         export function PileDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { dropAction: 'move', _forceActive: true, _freeform_noZoom: true, _freeform_noAutoPan: true, ...options, _type_collection: CollectionViewType.Pile }, id);
+            return InstanceFromProto(
+                Prototypes.get(DocumentType.COL),
+                new List(documents),
+                { backgroundColor: 'transparent', dropAction: 'move', _forceActive: true, _freeform_noZoom: true, _freeform_noAutoPan: true, ...options, _type_collection: CollectionViewType.Pile },
+                id
+            );
         }
 
         export function LinearDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
@@ -1679,7 +1684,6 @@ export namespace DocUtils {
             newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - size;
             newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - size;
             newCollection._width = newCollection._height = size * 2;
-            newCollection._jitterRotation = 10;
             return newCollection;
         }
     }
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 94748e884..63ff348e3 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -44,7 +44,6 @@ export enum StyleProp {
     ShowCaption = 'layout_showCaption',
     TitleHeight = 'titleHeight', // Height of Title area
     ShowTitle = 'layout_showTitle', // whether to display a title on a Document (optional :hover suffix)
-    JitterRotation = 'jitterRotation', // whether documents should be randomly rotated
     BorderPath = 'customBorder', // border path for document view
     FontSize = 'fontSize', // size of text font
     FontFamily = 'fontFamily', // font family of text
@@ -98,7 +97,6 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
     const backgroundCol = () => props?.styleProvider?.(doc, props, StyleProp.BackgroundColor);
     const opacity = () => props?.styleProvider?.(doc, props, StyleProp.Opacity);
     const layout_showTitle = () => props?.styleProvider?.(doc, props, StyleProp.ShowTitle);
-    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);
     // prettier-ignore
     switch (property.split(':')[0]) {
         case StyleProp.TreeViewIcon:
@@ -188,8 +186,6 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
                           </div>
                       ),
                   };
-        case StyleProp.JitterRotation:
-            return Doc.IsComicStyle(doc) ? random(-1, 1, NumCast(doc?.x), NumCast(doc?.y)) * ((props?.PanelWidth() || 0) > (props?.PanelHeight() || 0) ? 5 : 10) : 0;
         case StyleProp.HeaderMargin:
             return ([CollectionViewType.Stacking, CollectionViewType.NoteTaking, CollectionViewType.Masonry, CollectionViewType.Tree].includes(doc?._type_collection as any) ||
                 (doc?.type === DocumentType.RTF && !layout_showTitle()?.includes('noMargin')) ||
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index 91be31289..bbd528e13 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -93,13 +93,14 @@ export class CollectionPileView extends CollectionSubView() {
             this.layoutDoc._freeform_panY = -10;
             this.props.Document._freeform_pileEngine = computePassLayout.name;
         } else {
-            const defaultSize = NumCast(this.rootDoc._starburstDiameter, 500);
+            const defaultSize = NumCast(this.rootDoc._starburstDiameter, 400);
             this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[Width]() / 2 - defaultSize / 2;
             this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[Height]() / 2 - defaultSize / 2;
             this.layoutDoc._freeform_pileWidth = this.layoutDoc[Width]();
             this.layoutDoc._freeform_pileHeight = this.layoutDoc[Height]();
             this.layoutDoc._freeform_panX = this.layoutDoc._freeform_panY = 0;
             this.layoutDoc._width = this.layoutDoc._height = defaultSize;
+            this.layoutDoc.background;
             this.props.Document._freeform_pileEngine = computeStarburstLayout.name;
         }
     });
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index b5e9994dd..e1455525e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1318,7 +1318,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 layout_showTitle={this.props.childlayout_showTitle}
                 dontRegisterView={this.props.dontRenderDocuments || this.props.dontRegisterView}
                 pointerEvents={this.pointerEvents}
-                //rotation={this.props.styleProvider?.(childLayout, this.props, StyleProp.JitterRotation) || 0}
                 //fitContentsToBox={this.props.fitContentsToBox || BoolCast(this.props.treeViewFreezeChildDimensions)} // bcz: check this
             />
         );
@@ -1358,6 +1357,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @observable _lightboxDoc: Opt<Doc>;
 
     getCalculatedPositions(params: { pair: { layout: Doc; data?: Doc }; index: number; collection: 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 = params.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
@@ -1368,11 +1368,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             layoutFrameNumber === undefined
                 ? { _width: Cast(childDocLayout._width, 'number'), _height: Cast(childDocLayout._height, 'number'), _rotation: Cast(childDocLayout._rotation, 'number'), x: childDoc.x, y: childDoc.y, opacity: this.props.childOpacity?.() }
                 : CollectionFreeFormDocumentView.getValues(childDoc, layoutFrameNumber);
+        // prettier-ignore
+        const rotation = Cast(_rotation,'number',
+            !this.layoutDoc._rotation_jitter ? null
+                : NumCast(this.layoutDoc._rotation_jitter) * random(-1, 1, NumCast(x), NumCast(y)) );
         return {
             x: Number.isNaN(NumCast(x)) ? 0 : NumCast(x),
             y: Number.isNaN(NumCast(y)) ? 0 : NumCast(y),
             z: Cast(z, 'number'),
-            rotation: Cast(_rotation, 'number'),
+            rotation: rotation,
             color: Cast(color, 'string') ? StrCast(color) : this.props.styleProvider?.(childDoc, this.props, StyleProp.Color),
             backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.getClusterColor(childDoc, this.props, StyleProp.BackgroundColor),
             opacity: !_width ? 0 : this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this.props.styleProvider?.(childDoc, this.props, StyleProp.Opacity),
diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts
index 322d6458c..354f809e0 100644
--- a/src/server/server_Initialization.ts
+++ b/src/server/server_Initialization.ts
@@ -100,7 +100,7 @@ function buildWithMiddleware(server: express.Express) {
         passport.session(),
         (req: express.Request, res: express.Response, next: express.NextFunction) => {
             res.locals.user = req.user;
-            if ((req.originalUrl.endsWith('.png') || (process.env.RELEASE === 'true' && req.originalUrl.endsWith(".js"))) && req.method === 'GET' ) {
+            if ((req.originalUrl.endsWith('.png') || req.originalUrl.endsWith('.jpg') || (process.env.RELEASE === 'true' && req.originalUrl.endsWith('.js'))) && req.method === 'GET') {
                 const period = 30000;
                 res.set('Cache-control', `public, max-age=${period}`);
             } else {
-- 
cgit v1.2.3-70-g09d2

