From 3b1f0f8985b9996dae959f26f998190734727561 Mon Sep 17 00:00:00 2001
From: geireann <geireann.lindfield@gmail.com>
Date: Thu, 13 Mar 2025 11:54:01 -0400
Subject: pivot doc datetime fix.

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

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 241a56a88..4ea1de680 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -107,7 +107,7 @@ export function computePassLayout(poolData: Map<string, PoolData>, pivotDoc: Doc
 }
 
 function toNumber(val: FieldResult<FieldType>) {
-    return val === undefined ? undefined : DateCast(val) ? DateCast(val).date.getMilliseconds() : NumCast(val, Number(StrCast(val)));
+    return val === undefined ? undefined : DateCast(val) ? DateCast(val).date.getTime() : NumCast(val, Number(StrCast(val)));
 }
 
 export function computeStarburstLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[] /* , engineProps: any */) {
-- 
cgit v1.2.3-70-g09d2


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

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

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

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

