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

---
 src/client/views/collections/collectionGrid/CollectionGridView.tsx | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

(limited to 'src/client/views/collections/collectionGrid/CollectionGridView.tsx')

diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index e7f1de7f1..8393c719b 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -4,7 +4,7 @@ import * as React from 'react';
 import { returnFalse, returnZero, setupMoveUpEvents } from '../../../../ClientUtils';
 import { Doc, Opt } from '../../../../fields/Doc';
 import { Id } from '../../../../fields/FieldSymbols';
-import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
+import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { emptyFunction } from '../../../../Utils';
 import { Docs } from '../../../documents/Documents';
 import { DragManager } from '../../../util/DragManager';
@@ -16,6 +16,7 @@ import { DocumentView } from '../../nodes/DocumentView';
 import { CollectionSubView, SubCollectionViewProps } from '../CollectionSubView';
 import './CollectionGridView.scss';
 import Grid, { Layout } from './Grid';
+import { DocData } from '../../../../fields/DocSymbols';
 
 @observer
 export class CollectionGridView extends CollectionSubView() {
@@ -192,7 +193,7 @@ export class CollectionGridView extends CollectionSubView() {
         <DocumentView
             {...this._props}
             Document={layout}
-            TemplateDataDocument={layout.resolvedDataDoc as Doc}
+            TemplateDataDocument={DocCast(layout.rootDocument)?.[DocData]}
             NativeWidth={returnZero}
             NativeHeight={returnZero}
             fitWidth={this._props.childLayoutFitWidth}
-- 
cgit v1.2.3-70-g09d2


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

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

(limited to 'src/client/views/collections/collectionGrid/CollectionGridView.tsx')

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


From e8f1d494d36a5e1f1ee33d59faa4be2559cd752e Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 22 Apr 2025 10:54:38 -0400
Subject: adding drop handling code for scrapbookBox

---
 src/client/documents/DocUtils.ts                   | 60 +++++++--------
 src/client/views/DocumentDecorations.tsx           |  2 +-
 src/client/views/InkingStroke.tsx                  |  2 +-
 src/client/views/Main.tsx                          |  4 +-
 .../views/collections/CollectionStackingView.tsx   |  5 +-
 src/client/views/collections/CollectionSubView.tsx |  3 +-
 src/client/views/collections/CollectionView.tsx    | 20 ++---
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +
 .../collectionGrid/CollectionGridView.tsx          |  1 +
 .../collectionLinear/CollectionLinearView.tsx      |  1 +
 .../CollectionMulticolumnView.tsx                  |  1 +
 .../CollectionMultirowView.tsx                     |  1 +
 src/client/views/nodes/FieldView.tsx               |  2 +-
 src/client/views/nodes/FontIconBox/FontIconBox.tsx |  4 +-
 src/client/views/nodes/ImageBox.tsx                |  5 +-
 src/client/views/nodes/scrapbook/ScrapbookBox.tsx  | 87 ++++++++++++----------
 16 files changed, 104 insertions(+), 96 deletions(-)

(limited to 'src/client/views/collections/collectionGrid/CollectionGridView.tsx')

diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts
index db0770b12..df14dce5a 100644
--- a/src/client/documents/DocUtils.ts
+++ b/src/client/documents/DocUtils.ts
@@ -308,13 +308,7 @@ export namespace DocUtils {
      */
     export async function DocumentFromType(type: string, path: string, options: DocumentOptions, overwriteDoc?: Doc): Promise<Opt<Doc>> {
         let ctor: ((path: string, options: DocumentOptions, overwriteDoc?: Doc) => Doc | Promise<Doc | undefined>) | undefined;
-       
-        if (type.indexOf('scrapbook') !== -1) { 
-            ctor = Docs.Create.ScrapbookDocument;
-            if (!options._width) options._width = 400;
-            if (!options._title) options._title = 'New Scrapbook';
-        }
-        
+
         if (type.indexOf('image') !== -1) {
             ctor = Docs.Create.ImageDocument;
             if (!options._width) options._width = 300;
@@ -718,33 +712,31 @@ export namespace DocUtils {
                       nativeWidth: 40,
                       nativeHeight: 40,
                   })
-                : (defaultTextTemplate?.type === DocumentType.JOURNAL ? Docs.Create.DailyJournalDocument:Docs.Create.TextDocument)(
-                    '', 
-                    {
-                        annotationOn,
-                        backgroundColor,
-                        x,
-                        y,
-                        title,
-                        ...(defaultTextTemplate
-                            ? {} // if the new doc will inherit from a template, don't set any layout fields since that would block the inheritance
-                            : {
-                                  _width: width || BoolCast(Doc.UserDoc().fitBox) ? Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')) * 1.5 * 6 : 200,
-                                  _height: BoolCast(Doc.UserDoc().fitBox) ? Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')) * 1.5 : 35,
-                                  _layout_autoHeight: true,
-                                  backgroundColor: StrCast(Doc.UserDoc().textBackgroundColor),
-                                  borderColor: Doc.UserDoc().borderColor as string,
-                                  borderWidth: Doc.UserDoc().borderWidth as number,
-                                  text_centered: BoolCast(Doc.UserDoc().textCentered),
-                                  text_fitBox: BoolCast(Doc.UserDoc().fitBox),
-                                  text_align: StrCast(Doc.UserDoc().textAlign),
-                                  text_fontColor: StrCast(Doc.UserDoc().fontColor),
-                                  text_fontFamily: StrCast(Doc.UserDoc().fontFamily),
-                                  text_fontWeight: StrCast(Doc.UserDoc().fontWeight),
-                                  text_fontStyle: StrCast(Doc.UserDoc().fontStyle),
-                                  text_fontDecoration: StrCast(Doc.UserDoc().fontDecoration),
-                              }),
-                    });
+                : (defaultTextTemplate?.type === DocumentType.JOURNAL ? Docs.Create.DailyJournalDocument : Docs.Create.TextDocument)('', {
+                      annotationOn,
+                      backgroundColor,
+                      x,
+                      y,
+                      title,
+                      ...(defaultTextTemplate
+                          ? {} // if the new doc will inherit from a template, don't set any layout fields since that would block the inheritance
+                          : {
+                                _width: width || BoolCast(Doc.UserDoc().fitBox) ? Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')) * 1.5 * 6 : 200,
+                                _height: BoolCast(Doc.UserDoc().fitBox) ? Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')) * 1.5 : 35,
+                                _layout_autoHeight: true,
+                                backgroundColor: StrCast(Doc.UserDoc().textBackgroundColor),
+                                borderColor: Doc.UserDoc().borderColor as string,
+                                borderWidth: Doc.UserDoc().borderWidth as number,
+                                text_centered: BoolCast(Doc.UserDoc().textCentered),
+                                text_fitBox: BoolCast(Doc.UserDoc().fitBox),
+                                text_align: StrCast(Doc.UserDoc().textAlign),
+                                text_fontColor: StrCast(Doc.UserDoc().fontColor),
+                                text_fontFamily: StrCast(Doc.UserDoc().fontFamily),
+                                text_fontWeight: StrCast(Doc.UserDoc().fontWeight),
+                                text_fontStyle: StrCast(Doc.UserDoc().fontStyle),
+                                text_fontDecoration: StrCast(Doc.UserDoc().fontDecoration),
+                            }),
+                  });
 
         if (defaultTextTemplate) {
             tbox.layout_fieldKey = 'layout_' + StrCast(defaultTextTemplate.title);
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 7424aaf2c..3f11a4713 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -240,7 +240,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                         } else {
                             // if Doc is in the sticker palette, remove the flag indicating that it's saved
                             const dragFactory = DocCast(iconView.Document.dragFactory);
-                            if (dragFactory && DocCast(dragFactory.cloneOf).savedAsSticker) DocCast(dragFactory.cloneOf).savedAsSticker = undefined;
+                            if (dragFactory && DocCast(dragFactory.cloneOf)?.savedAsSticker) DocCast(dragFactory.cloneOf)!.savedAsSticker = undefined;
 
                             // if this is a face Annotation doc, then just hide it.
                             if (iconView.Document.annotationOn && iconView.Document.face) iconView.Document.hidden = true;
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index f555808ef..0a49ea28e 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -245,7 +245,7 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
      * factor for converting between ink and screen space.
      */
     inkScaledData = () => {
-        const inkData = Cast(this.dataDoc[this.fieldKey], InkField, Cast(this.layoutDoc[this.fieldKey], InkField, null))?.inkData ?? [];
+        const inkData = Cast(this.dataDoc[this.fieldKey], InkField, Cast(this.layoutDoc[this.fieldKey], InkField, null) ?? null)?.inkData ?? [];
         const inkStrokeWidth = NumCast(this.layoutDoc.stroke_width, 1);
         const inkTop = Math.min(...inkData.map(p => p.Y)) - inkStrokeWidth / 2;
         const inkBottom = Math.max(...inkData.map(p => p.Y)) + inkStrokeWidth / 2;
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 7992ed412..b884eb8c8 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -53,7 +53,6 @@ import { WebBox } from './nodes/WebBox';
 import { CalendarBox } from './nodes/calendarBox/CalendarBox';
 import { ChatBox } from './nodes/chatbot/chatboxcomponents/ChatBox';
 import { DailyJournal } from './nodes/formattedText/DailyJournal';
-import { ScrapbookVersionTwo } from './nodes/scrapbook/ScrapbookVersionTwo';
 import { DashDocCommentView } from './nodes/formattedText/DashDocCommentView';
 import { DashDocView } from './nodes/formattedText/DashDocView';
 import { DashFieldView } from './nodes/formattedText/DashFieldView';
@@ -66,6 +65,7 @@ import { PresBox, PresSlideBox } from './nodes/trails';
 import { FaceRecognitionHandler } from './search/FaceRecognitionHandler';
 import { SearchBox } from './search/SearchBox';
 import { StickerPalette } from './smartdraw/StickerPalette';
+import { ScrapbookBox } from './nodes/scrapbook/ScrapbookBox';
 
 dotenv.config();
 
@@ -121,7 +121,6 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0, message: 'cache' };
             FormattedTextBox,
             DailyJournal, // AARAV
             ImageBox,
-            ScrapbookVersionTwo,
             FontIconBox,
             LabelBox,
             EquationBox,
@@ -136,6 +135,7 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0, message: 'cache' };
             VideoBox,
             AudioBox,
             RecordingBox,
+            ScrapbookBox,
             PresBox,
             PresSlideBox,
             SearchBox,
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 9155227dd..f11e646cc 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -343,7 +343,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
     childFitWidth = (doc: Doc) => Cast(this.Document.childLayoutFitWidth, 'boolean', this._props.childLayoutFitWidth?.(doc) ?? Cast(doc.layout_fitWidth, 'boolean', null) ?? null);
     // this is what renders the document that you see on the screen
     // called in Children: this actually adds a document to our children list
-    getDisplayDoc(doc: Doc, trans: () => string, count: number) {
+    getDisplayDoc = (doc: Doc, trans: () => string, count: number) => {
         const dataDoc = doc.isTemplateDoc || doc.isTemplateForField ? this._props.TemplateDataDocument : undefined;
         this._docXfs.push({ stackedDocTransform: this.getDocTransform(doc), width: this.getDocWidth(doc), height: this.getDocHeight(doc) });
         return count > this._renderCount ? null : (
@@ -384,6 +384,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 searchFilterDocs={this.searchFilterDocs}
                 xPadding={NumCast(this.layoutDoc._childXPadding, this._props.childXPadding)}
                 yPadding={NumCast(this.layoutDoc._childYPadding, this._props.childYPadding)}
+                rejectDrop={this._props.childRejectDrop}
                 addDocument={this._props.addDocument}
                 moveDocument={this._props.moveDocument}
                 removeDocument={this._props.removeDocument}
@@ -393,7 +394,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 pinToPres={this._props.pinToPres}
             />
         );
-    }
+    };
 
     getDocTransform = computedFn((doc: Doc) => () => {
         // these must be referenced for document decorations to update when the text box container is scrolled
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index bc7d6f897..9a0fda3f8 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -58,6 +58,7 @@ export interface CollectionViewProps extends React.PropsWithChildren<FieldViewPr
     childOpacity?: () => number;
     childContextMenuItems?: () => { script: ScriptField; label: string }[];
     childLayoutTemplate?: () => Doc | undefined; // specify a layout Doc template to use for children of the collection
+    childRejectDrop?: (draggedDoc: Doc[] | undefined, subView?: DocumentView) => boolean; // whether a child document can be dropped on this document
     childHideDecorationTitle?: boolean;
     childHideResizeHandles?: boolean;
     childHideDecorations?: boolean;
@@ -323,7 +324,7 @@ export function CollectionSubView<X>() {
 
         protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean {
             const { docDragData } = de.complete;
-            if (docDragData && !docDragData.draggedDocuments.includes(this.Document)) {
+            if (docDragData && !docDragData.draggedDocuments.includes(this.Document) && !this._props.rejectDrop?.(docDragData.draggedDocuments, this.DocumentView?.())) {
                 let added;
                 const dropAction = docDragData.dropAction || docDragData.userDropAction;
                 const targetDocments = DocListCast(this.dataDoc[this._props.fieldKey]);
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index c9af92a1b..eb9caf29d 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -183,15 +183,17 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
                         },
                     })
                 );
-                DocListCast(Cast(Doc.UserDoc()['clickFuncs-child'], Doc, null)?.data).forEach(childClick =>
-                    onClicks.push({
-                        description: `Set child ${childClick.title}`,
-                        icon: 'edit',
-                        event: () => {
-                            this.dataDoc[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data));
-                        },
-                    })
-                );
+                DocListCast(Cast(Doc.UserDoc()['clickFuncs-child'], Doc, null)?.data)
+                    .filter(childClick => ScriptCast(childClick.data))
+                    .forEach(childClick =>
+                        onClicks.push({
+                            description: `Set child ${childClick.title}`,
+                            icon: 'edit',
+                            event: () => {
+                                this.dataDoc[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data)!);
+                            },
+                        })
+                    );
                 !Doc.IsSystem(this.Document) && !existingOnClick && cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
             }
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 842293358..08126d4fe 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -496,6 +496,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     }, 'link drop');
 
     onInternalDrop = (e: Event, de: DragManager.DropEvent): boolean => {
+        if (this._props.rejectDrop?.(de.complete.docDragData?.draggedDocuments, this._props.DocumentView?.())) return false;
         if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de, de.complete.annoDragData);
         if (de.complete.linkDragData) return this.internalLinkDrop(e, de, de.complete.linkDragData);
         if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData);
@@ -1562,6 +1563,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 pinToPres={this._props.pinToPres}
                 whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
                 dragAction={(this.Document.childDragAction ?? this._props.childDragAction) as dropActionType}
+                rejectDrop={this._props.childRejectDrop}
                 showTitle={this._props.childlayout_showTitle}
                 dontRegisterView={this._props.dontRegisterView}
                 pointerEvents={this.childPointerEventsFunc}
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index a7603a45b..1d5e70be7 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -201,6 +201,7 @@ export class CollectionGridView extends CollectionSubView() {
             isContentActive={this.isChildContentActive}
             PanelWidth={width}
             PanelHeight={height}
+            rejectDrop={this._props.childRejectDrop}
             ScreenToLocalTransform={dxf}
             setContentViewBox={emptyFunction}
             whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index 8d3947653..3c2a99b1e 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -192,6 +192,7 @@ export class CollectionLinearView extends CollectionSubView() {
                     renderDepth={this._props.renderDepth + 1}
                     dontRegisterView={BoolCast(this.Document.childDontRegisterViews)}
                     focus={emptyFunction}
+                    rejectDrop={this._props.childRejectDrop}
                     styleProvider={this._props.styleProvider}
                     containerViewPath={this.childContainerViewPath}
                     whenChildContentsActiveChanged={emptyFunction}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index adaceb053..caad1c7e0 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -281,6 +281,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
             PanelWidth={this.childWidth(childLayout)}
             PanelHeight={this.childHeight}
             rootSelected={this.rootSelected}
+            rejectDrop={this._props.childRejectDrop}
             dragAction={StrCast(this.Document.childDragAction, this._props.childDragAction) as dropActionType}
             onClickScript={this.onChildClickHandler}
             onDoubleClickScript={this.onChildDoubleClickHandler}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 27c41583c..593598479 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -263,6 +263,7 @@ export class CollectionMultirowView extends CollectionSubView() {
             PanelWidth={this.childWidth}
             PanelHeight={this.childHeight(childLayout)}
             rootSelected={this.rootSelected}
+            rejectDrop={this._props.childRejectDrop}
             dragAction={StrCast(this.Document.childDragAction, this._props.childDragAction) as dropActionType}
             onClickScript={this.onChildClickHandler}
             onDoubleClickScript={this.onChildDoubleClickHandler}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index de49f502f..b4473d9dc 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -68,7 +68,7 @@ export interface FieldViewSharedProps {
     isGroupActive?: () => string | undefined; // is this document part of a group that is active
     // eslint-disable-next-line no-use-before-define
     setContentViewBox?: (view: ViewBoxInterface<FieldViewProps>) => void; // called by rendered field's viewBox so that DocumentView can make direct calls to the viewBox
-
+    rejectDrop?: (draggedDoc: Doc[] | undefined, subView?: DocumentView) => boolean; // whether a document drop is rejected
     PanelWidth: () => number;
     PanelHeight: () => number;
     isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index 3190757e2..5941e1669 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -117,10 +117,10 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
             default:                               type = 'slider';
                 break;
         } // prettier-ignore
-        const numScript = (value?: number) => ScriptCast(this.Document.script).script.run({ this: this.Document, value, _readOnly_: value === undefined });
+        const numScript = (value?: number) => ScriptCast(this.Document.script)?.script.run({ this: this.Document, value, _readOnly_: value === undefined });
         const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) as string;
         // Script for checking the outcome of the toggle
-        const checkResult = Number(Number(numScript().result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3)));
+        const checkResult = Number(Number(numScript()?.result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3)));
 
         return (
             <NumberDropdown
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 7ad3ee2b6..c59c38fd8 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -2,7 +2,7 @@ import { Button, Colors, EditableText, Size, Type } from '@dash/components';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Slider, Tooltip } from '@mui/material';
 import axios from 'axios';
-import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { extname } from 'path';
 import * as React from 'react';
@@ -213,7 +213,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     drop = undoable(
         action((e: Event, de: DragManager.DropEvent) => {
-            if (de.complete.docDragData) {
+            if (de.complete.docDragData && this._props.rejectDrop?.(de.complete.docDragData?.draggedDocuments, this.DocumentView?.())) {
                 let added: boolean | undefined;
                 const hitDropTarget = (ele: HTMLElement, dropTarget: HTMLDivElement | null): boolean => {
                     if (!ele) return false;
@@ -938,6 +938,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                         ScreenToLocalTransform={this.screenToLocalTransform}
                         select={emptyFunction}
                         focus={this.focus}
+                        rejectDrop={this._props.rejectDrop}
                         getScrollHeight={this.getScrollHeight}
                         NativeDimScaling={returnOne}
                         isAnyChildContentActive={returnFalse}
diff --git a/src/client/views/nodes/scrapbook/ScrapbookBox.tsx b/src/client/views/nodes/scrapbook/ScrapbookBox.tsx
index 56cfcda70..b02976067 100644
--- a/src/client/views/nodes/scrapbook/ScrapbookBox.tsx
+++ b/src/client/views/nodes/scrapbook/ScrapbookBox.tsx
@@ -1,24 +1,25 @@
-import { makeObservable } from 'mobx';
+import { action, makeObservable, observable } from 'mobx';
 import * as React from 'react';
-import { ViewBoxAnnotatableComponent } from '../../DocComponent';
-import { FieldView, FieldViewProps } from '../FieldView';
+import { Doc } from '../../../../fields/Doc';
+import { List } from '../../../../fields/List';
+import { emptyFunction } from '../../../../Utils';
 import { Docs } from '../../../documents/Documents';
 import { DocumentType } from '../../../documents/DocumentTypes';
-import { action, observable } from 'mobx';
-import { DocListCast } from '../../../../fields/Doc';
-import { Doc } from '../../../../fields/Doc';
+import { CollectionView } from '../../collections/CollectionView';
+import { ViewBoxAnnotatableComponent } from '../../DocComponent';
 import { DocumentView } from '../DocumentView';
-import { FormattedTextBox } from '../formattedText/FormattedTextBox';
-import { List } from '../../../../fields/List';
+import { FieldView, FieldViewProps } from '../FieldView';
+import { DragManager } from '../../../util/DragManager';
 // Scrapbook view: a container that lays out its child items in a grid/template
 export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     @observable createdDate: string;
+    private _dropDisposer?: DragManager.DragDropDisposer;
 
     constructor(props: FieldViewProps) {
         super(props);
         makeObservable(this);
         this.createdDate = this.getFormattedDate();
-  
+
         // ensure we always have a List<Doc> in dataDoc['items']
         if (!this.dataDoc[this.fieldKey]) {
             this.dataDoc[this.fieldKey] = new List<Doc>();
@@ -31,12 +32,6 @@ export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         return FieldView.LayoutString(ScrapbookBox, fieldStr);
     }
 
-
-    
-    
-     
-    
-
     getFormattedDate(): string {
         return new Date().toLocaleDateString(undefined, {
             year: 'numeric',
@@ -55,40 +50,50 @@ export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
 
     componentDidMount() {
         this.setTitle();
-        if (!this.dataDoc[this.fieldKey]) {
-            this.dataDoc[this.fieldKey] = new List<Doc>();
-        }
     }
 
+    childRejectDrop = (draggedDoc: Doc[] | undefined, subView?: DocumentView) => {
+        if (draggedDoc?.length === 1 && subView) {
+            if (subView.Document.type === DocumentType.IMG && draggedDoc[0].$type !== DocumentType.IMG) {
+                return true;
+            }
+        }
+        return false;
+    };
+    rejectDrop = (draggedDoc: Doc[] | undefined, subView?: DocumentView) => {
+        if (draggedDoc?.length === 1 && draggedDoc[0].$type !== DocumentType.IMG) {
+            return true;
+        }
+        return false;
+    };
+    onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
+        if (de.complete.docDragData?.draggedDocuments[0]?.$type === DocumentType.IMG) {
+            return true;
+        }
+        return false;
+    };
+
+    protected createDashEventsTarget = (ele: HTMLDivElement | null) => {
+        this._dropDisposer?.();
+        if (ele) {
+            this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
+        }
+    };
+
     render() {
-        // cast into an array even if empty
-        const items: Doc[] = DocListCast(this.dataDoc[this.fieldKey]);
-    
         return (
-            <div style={{ background: 'beige', width: '100%', height: '100%' }}>
-                        
+            <div style={{ background: 'beige', width: '100%', height: '100%' }} ref={r => r && this.createDashEventsTarget(r)}>
+                <CollectionView
+                    {...this._props} //
+                    setContentViewBox={emptyFunction}
+                    rejectDrop={this.rejectDrop}
+                    childRejectDrop={this.childRejectDrop}
+                />
             </div>
-        
-        //   <div
-        //     style={{
-        //     }}
-        //   >
-        //     {items.length === 0
-        //       ? <div style={{color:'#888',fontStyle:'italic'}}>Drop docs here</div>
-        //       : items.map((childDoc, idx) => (
-        //           <DocumentView
-        //             key={String(childDoc.id ?? idx)}
-        //             {...this._props}
-        //             Document={childDoc}
-        //           />
-        //         ))
-        //     }
-        //   </div>
         );
-      }
+    }
 }
 
-
 // Register scrapbook
 Docs.Prototypes.TemplateMap.set(DocumentType.SCRAPBOOK, {
     layout: { view: ScrapbookBox, dataField: 'items' },
-- 
cgit v1.2.3-70-g09d2


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

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

(limited to 'src/client/views/collections/collectionGrid/CollectionGridView.tsx')

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


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

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

(limited to 'src/client/views/collections/collectionGrid/CollectionGridView.tsx')

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

