From b60b1863be5ecf987f3d3f2483e28e74f634853f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 25 Apr 2025 16:02:18 -0400
Subject: fixed face collection to show up.  fixed pasting text dirextly into
 freeformview to create docs properly. also fixed paste to match source text
 styling better.  fixed copying out of pdf.  fixed formatting of gpt (())
 calls from text box by using paste handling code.

---
 .../views/nodes/formattedText/FormattedTextBox.tsx | 62 ++++++++++++++++++++--
 1 file changed, 58 insertions(+), 4 deletions(-)

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

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 9897a0062..164c64107 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1344,8 +1344,63 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         return text;
     };
 
-    handlePaste = (view: EditorView, event: Event /* , slice: Slice */): boolean => {
-        const pdfAnchorId = (event as ClipboardEvent).clipboardData?.getData('dash/pdfAnchor');
+    handlePaste = (view: EditorView, event: ClipboardEvent /* , slice: Slice */): boolean => {
+        return this.doPaste(view, event.clipboardData);
+    };
+    doPaste = (view: EditorView, data: DataTransfer | null) => {
+        const html = data?.getData('text/html');
+        const text = data?.getData('text/plain');
+        const pdfAnchorId = data?.getData('dash/pdfAnchor');
+        if (html && !pdfAnchorId) {
+            const replaceDivsWithParagraphs = (expr: string) => {
+                // Create a temporary DOM container
+                const container = document.createElement('div');
+                container.innerHTML = expr;
+
+                // Recursive function to process all divs
+                function processDivs(node: HTMLElement) {
+                    // Get all div elements in the current node (live collection)
+                    const divs = node.getElementsByTagName('div');
+
+                    // We need to convert to array because we'll be modifying the DOM
+                    const divsArray = Array.from(divs);
+
+                    for (const div of divsArray) {
+                        // Create replacement paragraph
+                        const p = document.createElement('p');
+
+                        // Copy all attributes
+                        for (const attr of div.attributes) {
+                            p.setAttribute(attr.name, attr.value);
+                        }
+
+                        // Move all child nodes
+                        while (div.firstChild) {
+                            p.appendChild(div.firstChild);
+                        }
+
+                        // Replace the div with the paragraph
+                        div.parentNode?.replaceChild(p, div);
+
+                        // Process any nested divs that were moved into the new paragraph
+                        processDivs(p);
+                    }
+                }
+
+                // Start processing from the container
+                processDivs(container);
+
+                return container.innerHTML;
+            };
+            const fixedHTML = replaceDivsWithParagraphs(html);
+            // .replace(/<div\b([^>]*)>(.*?)<\/div>/g, '<p$1>$2</p>'); // prettier-ignore
+            this._inDrop = true;
+            view.pasteHTML(html.split('<p').length < 2 ? fixedHTML : html);
+            this._inDrop = false;
+
+            return true;
+        }
+
         return !!(pdfAnchorId && this.addPdfReference(pdfAnchorId));
     };
 
@@ -1485,9 +1540,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         }
         if (this._props.isContentActive()) this.prepareForTyping();
         if (this.EditorView && FormattedTextBox.PasteOnLoad) {
-            const pdfAnchorId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfAnchor');
+            this.doPaste(this.EditorView, FormattedTextBox.PasteOnLoad.clipboardData);
             FormattedTextBox.PasteOnLoad = undefined;
-            pdfAnchorId && this.addPdfReference(pdfAnchorId);
         }
         if (this._props.autoFocus) setTimeout(() => this.EditorView!.focus()); // not sure why setTimeout is needed but editing dashFieldView's doesn't work without it.
     }
-- 
cgit v1.2.3-70-g09d2


From a25dc02334de3f5b58aff1911bdae30d49a1d26b Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 28 Apr 2025 12:52:29 -0400
Subject: cleaned up outpainting and Doc resize code.  fixes problems with doc
 resizing of text boxes with ctrl-key.

---
 src/client/documents/Documents.ts                  |   2 -
 src/client/views/DocumentDecorations.tsx           | 106 +++++++--------------
 src/client/views/nodes/ImageBox.tsx                |  10 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |   2 +-
 src/server/ApiManagers/FireflyManager.ts           |   8 +-
 5 files changed, 39 insertions(+), 89 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index e694419a4..a4a668085 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -535,8 +535,6 @@ export class DocumentOptions {
      * The list of embedded Doc instances in each Scrapbook slot
      */
     scrapbookContents?: List<Doc>;
-
-    _outpaintingMetadata?: STRt = new StrInfo('serialized JSON metadata needed for image outpainting', false);
 }
 
 export const DocOptions = new DocumentOptions();
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 3f11a4713..2d39b827d 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -445,6 +445,12 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
     @action
     onPointerDown = (e: React.PointerEvent): void => {
         SnappingManager.SetIsResizing(DocumentView.Selected().lastElement()?.Document[Id]); // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them
+        DocumentView.Selected()
+            .filter(dv => e.shiftKey && dv.ComponentView instanceof ImageBox)
+            .forEach(dv => {
+                dv.Document._outpaintingOriginalWidth = NumCast(dv.Document._width);
+                dv.Document._outpaintingOriginalHeight = NumCast(dv.Document._height);
+            });
         setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction);
         e.stopPropagation();
         const id = (this._resizeHdlId = e.currentTarget.className);
@@ -484,50 +490,31 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         const dragHdl = this._resizeHdlId.split(' ')[0].replace('documentDecorations-', '').replace('Resizer', '');
 
         const thisPt = // do snapping of drag point
-            fixedAspect && (dragHdl === 'bottomRight' || dragHdl === 'topLeft')
+            fixedAspect && (dragHdl === 'bottomRight' || dragHdl === 'topLeft') && e.ctrlKey
                 ? DragManager.snapDragAspect(this.projectDragToAspect(e, first, fixedAspect), fixedAspect)
                 : DragManager.snapDrag(e, -this._offset.x, -this._offset.y, this._offset.x, this._offset.y);
 
         const { scale, refPt } = this.getResizeVals(thisPt, dragHdl);
 
         !this._interactionLock &&
-            runInAction(async () => {
+            runInAction(() => {
                 // resize selected docs if we're not in the middle of a resize (ie, throttle input events to frame rate)
                 this._interactionLock = true;
                 this._snapPt = thisPt;
 
-                // Special handling for shift+click (outpainting mode)
-                if (e.shiftKey && DocumentView.Selected().some(dv => dv.ComponentView instanceof ImageBox)) {
-                    DocumentView.Selected().forEach(docView => {
-                        if (docView.ComponentView instanceof ImageBox) {
-                            // Set flag for outpainting mode
-                            docView.Document._outpaintingResize = true;
+                const notOutpainted = DocumentView.Selected().filter(dv => !e.shiftKey || !(dv.ComponentView instanceof ImageBox));
+                // Special handling for shift-drag resize (outpainting of Images)
+                DocumentView.Selected()
+                    .filter(dv => !notOutpainted.includes(dv))
+                    .forEach(dv => this.resizeViewForOutpainting(dv, refPt, scale, { dragHdl, shiftKey: e.shiftKey })); // Adjust only the document dimensions without scaling internal content
 
-                            // Adjust only the document dimensions without scaling internal content
-                            this.resizeViewForOutpainting(docView, refPt, scale, { dragHdl, shiftKey: e.shiftKey });
-                        } else {
-                            // Handle regular resize for non-image components
-                            e.ctrlKey && !Doc.NativeHeight(docView.Document) && docView.toggleNativeDimensions();
-                            const hasFixedAspect = this.hasFixedAspect(docView.Document);
-                            const scaleAspect = { x: scale.x === 1 && hasFixedAspect ? scale.y : scale.x, y: scale.x !== 1 && hasFixedAspect ? scale.x : scale.y };
-                            this.resizeView(docView, refPt, scaleAspect, { dragHdl, ctrlKey: e.ctrlKey });
-                        }
-                    });
-                } else {
-                    // Regular resize behavior (existing code)
-                    e.ctrlKey && DocumentView.Selected().forEach(docView => !Doc.NativeHeight(docView.Document) && docView.toggleNativeDimensions());
-                    const hasFixedAspect = DocumentView.Selected()
-                        .map(dv => dv.Document)
-                        .some(this.hasFixedAspect);
-                    const scaleAspect = { x: scale.x === 1 && hasFixedAspect ? scale.y : scale.x, y: scale.x !== 1 && hasFixedAspect ? scale.x : scale.y };
-                    DocumentView.Selected().forEach(docView => this.resizeView(docView, refPt, scaleAspect, { dragHdl, ctrlKey: e.ctrlKey }));
-                }
+                // Regular resize behavior for docs not being outpainted
+                e.ctrlKey && notOutpainted.forEach(docView => !Doc.NativeHeight(docView.Document) && docView.toggleNativeDimensions());
+                const hasFixedAspect = notOutpainted.map(dv => dv.Document).some(this.hasFixedAspect);
+                const scaleAspect = { x: scale.x === 1 && hasFixedAspect ? scale.y : scale.x, y: scale.x !== 1 && hasFixedAspect ? scale.x : scale.y };
+                notOutpainted.forEach(docView => this.resizeView(docView, refPt, scaleAspect, { dragHdl, freezeNativeDims: e.ctrlKey }));
 
-                await new Promise<void>(res => {
-                    setTimeout(() => {
-                        res((this._interactionLock = undefined));
-                    });
-                });
+                new Promise<void>(res => setTimeout(() => res((this._interactionLock = undefined))));
             });
 
         return false;
@@ -545,11 +532,6 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
             return;
         }
 
-        if (!doc._outpaintingOriginalWidth || !doc._outpaintingOriginalHeight) {
-            doc._outpaintingOriginalWidth = NumCast(doc._width);
-            doc._outpaintingOriginalHeight = NumCast(doc._height);
-        }
-
         // Calculate new boundary dimensions
         const originalWidth = NumCast(doc._width);
         const originalHeight = NumCast(doc._height);
@@ -566,38 +548,17 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         doc.y = NumCast(doc.y) + deltaY;
 
         doc._layout_modificationDate = new DateField();
-
-        // Trigger outpainting
-        doc._needsOutpainting = true;
-
-        // Store metadata needed for outpainting
-        doc._outpaintingMetadata = JSON.stringify({
-            originalWidth: doc._outpaintingOriginalWidth,
-            originalHeight: doc._outpaintingOriginalHeight,
-            newWidth,
-            newHeight,
-            scaleX: scale.x,
-            scaleY: scale.y,
-            anchorHandle: opts.dragHdl,
-        });
     };
 
     @action
-    onPointerUp = (): void => {
+    onPointerUp = (e: PointerEvent): void => {
         SnappingManager.SetIsResizing(undefined);
         SnappingManager.clearSnapLines();
 
         // Check if any outpainting needs to be processed
-        DocumentView.Selected().forEach(view => {
-            if (view.Document._needsOutpainting && view.ComponentView instanceof ImageBox) {
-                // Trigger outpainting process in the ImageBox component
-                (view.ComponentView as ImageBox).processOutpainting();
-
-                // Clear flags
-                view.Document._needsOutpainting = false;
-                view.Document._outpaintingResize = false;
-            }
-        });
+        DocumentView.Selected()
+            .filter(dv => e.shiftKey && dv.ComponentView instanceof ImageBox)
+            .forEach(view => (view.ComponentView as ImageBox).processOutpainting());
 
         this._resizeHdlId = '';
         this._resizeUndo?.end();
@@ -647,7 +608,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
     //
     // resize a single DocumentView about the specified reference point, possibly setting/updating the native dimensions of the Doc
     //
-    resizeView = (docView: DocumentView, refPt: number[], scale: { x: number; y: number }, opts: { dragHdl: string; ctrlKey: boolean }) => {
+    resizeView = (docView: DocumentView, refPt: number[], scale: { x: number; y: number }, opts: { dragHdl: string; freezeNativeDims: boolean }) => {
         const doc = docView.Document;
         if (doc.isGroup) {
             DocListCast(doc.data)
@@ -660,25 +621,24 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
             const [nwidth, nheight] = [docView.nativeWidth, docView.nativeHeight];
             const [initWidth, initHeight] = [NumCast(doc._width, 1), NumCast(doc._height)];
 
-            const modifyNativeDim =
-                (opts.ctrlKey && doc._layout_nativeDimEditable) || // e.g., PDF or web page
-                (doc._layout_reflowHorizontal && opts.dragHdl !== 'bottom' && opts.dragHdl !== 'top') || // eg rtf or some web pages
-                (doc._layout_reflowVertical && (opts.dragHdl === 'bottom' || opts.dragHdl === 'top' || opts.ctrlKey)); // eg rtf, web, pdf
-            if (nwidth && nheight && !modifyNativeDim) {
-                // eg., dragging right resizer on PDF -- enforce native dimensions because not expliclty overridden with ctrl or bottom resize drag
+            const cornerReflow = !opts.freezeNativeDims && doc._layout_reflowHorizontal && doc._layout_reflowVertical && ['topLeft', 'topRight', 'bottomLeft', 'bottomRight'].includes(opts.dragHdl);
+            const horizontalReflow = !opts.freezeNativeDims && doc._layout_reflowHorizontal && ['left', 'right'].includes(opts.dragHdl); // eg rtf or some web pages
+            const verticalReflow = !opts.freezeNativeDims && doc._layout_reflowVertical && ['bottom', 'top'].includes(opts.dragHdl); // eg rtf, web, pdf
+            // preserve aspect ratio if Doc has a native ize and drag won't cause reflow (eg, ctrl-dragging a Doc's corner doesn't allow reflow, or dragging right side of a PDF which isn't horizontally reflowable)
+            if (nwidth && nheight && !cornerReflow && !horizontalReflow && !verticalReflow) {
                 scale.x === 1 ? (scale.x = scale.y) : (scale.y = scale.x);
             }
 
-            if (['right', 'left', 'bottomRight'].includes(opts.dragHdl) && modifyNativeDim && Doc.NativeWidth(doc)) {
+            if ((horizontalReflow || cornerReflow) && Doc.NativeWidth(doc)) {
                 const setData = Doc.NativeWidth(doc[DocData]) === doc.nativeWidth;
-                doc.nativeWidth = scale.x * Doc.NativeWidth(doc);
+                doc._nativeWidth = scale.x * Doc.NativeWidth(doc);
                 if (setData) Doc.SetNativeWidth(doc[DocData], NumCast(doc.nativeWidth));
                 if (doc._layout_reflowVertical && !NumCast(doc.nativeHeight)) {
                     doc._nativeHeight = (initHeight / initWidth) * nwidth; // initializes the nativeHeight for a PDF
                 }
             }
-            if (['bottom', 'top', 'bottomRight'].includes(opts.dragHdl) && modifyNativeDim && Doc.NativeHeight(doc)) {
-                const setData = Doc.NativeHeight(doc[DocData]) === doc.nativeHeight && (!doc.layout_reflowVertical || opts.ctrlKey);
+            if ((verticalReflow || cornerReflow) && Doc.NativeHeight(doc)) {
+                const setData = Doc.NativeHeight(doc[DocData]) === doc.nativeHeight && !doc.layout_reflowVertical;
                 doc._nativeHeight = scale.y * Doc.NativeHeight(doc);
                 if (setData) Doc.SetNativeHeight(doc[DocData], NumCast(doc._nativeHeight));
             }
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 999d7089b..197ef0998 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -170,14 +170,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             },
             { fireImmediately: true }
         );
-        this._disposers.outpainting = reaction(
-            () => this.Document?._needsOutpainting,
-            needsOutpainting => {
-                if (needsOutpainting && this.Document?._outpaintingResize) {
-                    this.processOutpainting();
-                }
-            }
-        );
     }
 
     componentWillUnmount() {
@@ -421,7 +413,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             const response = await Networking.PostToServer('/outpaintImage', {
                 imageUrl: currentPath,
                 prompt: customPrompt,
-                originalDimensions: { width: origWidth, height: origHeight },
+                originalDimensions: { width: Math.min(newWidth, origWidth), height: Math.min(newHeight, origHeight) },
                 newDimensions: { width: newWidth, height: newHeight },
             });
 
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 164c64107..98e461a52 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1149,7 +1149,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     // Since we also monitor all component height changes, this will update the document's height.
     resetNativeHeight = action((scrollHeight: number) => {
         this.layoutDoc['_' + this.fieldKey + '_height'] = scrollHeight;
-        if (!this.layoutDoc.isTemplateForField) this.layoutDoc._nativeHeight = scrollHeight;
+        if (!this.layoutDoc.isTemplateForField && NumCast(this.layoutDoc._nativeHeight)) this.layoutDoc._nativeHeight = scrollHeight;
     });
 
     addPlugin = (plugin: Plugin) => {
diff --git a/src/server/ApiManagers/FireflyManager.ts b/src/server/ApiManagers/FireflyManager.ts
index 5311ca643..63581d3b3 100644
--- a/src/server/ApiManagers/FireflyManager.ts
+++ b/src/server/ApiManagers/FireflyManager.ts
@@ -343,10 +343,10 @@ export default class FireflyManager extends ApiManager {
                                             numVariations: 1,
                                             placement: {
                                                 inset: {
-                                                    left: 0,
-                                                    top: 0,
-                                                    right: Math.round(req.body.newDimensions.width - req.body.originalDimensions.width),
-                                                    bottom: Math.round(req.body.newDimensions.height - req.body.originalDimensions.height),
+                                                    left: Math.round(req.body.newDimensions.width - req.body.originalDimensions.width) / 2,
+                                                    top: Math.round(req.body.newDimensions.height - req.body.originalDimensions.height) / 2,
+                                                    right: Math.round(req.body.newDimensions.width - req.body.originalDimensions.width) / 2,
+                                                    bottom: Math.round(req.body.newDimensions.height - req.body.originalDimensions.height) / 2,
                                                 },
                                                 alignment: {
                                                     horizontal: 'center',
-- 
cgit v1.2.3-70-g09d2


From 0694277e33c4a5c4f66b1cebef38f9d86c38cf34 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 29 Apr 2025 12:29:02 -0400
Subject: clean up outpaint code.  fix image sizing when outpainting.

---
 src/client/views/DocumentDecorations.tsx           | 25 ++++++-------
 src/client/views/nodes/ImageBox.scss               | 42 +++++++++++-----------
 src/client/views/nodes/ImageBox.tsx                | 24 ++++++-------
 .../views/nodes/formattedText/FormattedTextBox.tsx |  1 -
 .../imageMeshTool/imageMeshToolButton.tsx          | 15 ++------
 5 files changed, 46 insertions(+), 61 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 2d39b827d..ab665e984 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -448,8 +448,8 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         DocumentView.Selected()
             .filter(dv => e.shiftKey && dv.ComponentView instanceof ImageBox)
             .forEach(dv => {
-                dv.Document._outpaintingOriginalWidth = NumCast(dv.Document._width);
-                dv.Document._outpaintingOriginalHeight = NumCast(dv.Document._height);
+                dv.Document[dv.ComponentView!.fieldKey + '_outpaintOriginalWidth'] = NumCast(dv.Document._width);
+                dv.Document[dv.ComponentView!.fieldKey + '_outpaintOriginalHeight'] = NumCast(dv.Document._height);
             });
         setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction);
         e.stopPropagation();
@@ -502,14 +502,15 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                 this._interactionLock = true;
                 this._snapPt = thisPt;
 
-                const notOutpainted = DocumentView.Selected().filter(dv => !e.shiftKey || !(dv.ComponentView instanceof ImageBox));
-                // Special handling for shift-drag resize (outpainting of Images)
-                DocumentView.Selected()
-                    .filter(dv => !notOutpainted.includes(dv))
-                    .forEach(dv => this.resizeViewForOutpainting(dv, refPt, scale, { dragHdl, shiftKey: e.shiftKey })); // Adjust only the document dimensions without scaling internal content
+                const outpainted = e.shiftKey ? DocumentView.Selected().filter(dv => dv.ComponentView instanceof ImageBox) : [];
+                const notOutpainted = e.shiftKey ? DocumentView.Selected().filter(dv => !outpainted.includes(dv)) : DocumentView.Selected();
 
-                // Regular resize behavior for docs not being outpainted
+                // Special handling for shift-drag resize (outpainting of Images by resizing without scaling content - fill in with firefly GAI)
+                e.shiftKey && outpainted.forEach(dv => this.resizeViewForOutpainting(dv, refPt, scale, { dragHdl, shiftKey: e.shiftKey }));
+
+                // Special handling for not outpainted Docs when ctrl-resizing (setup native dimesions for modification)
                 e.ctrlKey && notOutpainted.forEach(docView => !Doc.NativeHeight(docView.Document) && docView.toggleNativeDimensions());
+
                 const hasFixedAspect = notOutpainted.map(dv => dv.Document).some(this.hasFixedAspect);
                 const scaleAspect = { x: scale.x === 1 && hasFixedAspect ? scale.y : scale.x, y: scale.x !== 1 && hasFixedAspect ? scale.x : scale.y };
                 notOutpainted.forEach(docView => this.resizeView(docView, refPt, scaleAspect, { dragHdl, freezeNativeDims: e.ctrlKey }));
@@ -546,20 +547,14 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         const { deltaX, deltaY } = this.realignRefPt(doc, refCent, originalWidth, originalHeight);
         doc.x = NumCast(doc.x) + deltaX;
         doc.y = NumCast(doc.y) + deltaY;
-
         doc._layout_modificationDate = new DateField();
     };
 
     @action
-    onPointerUp = (e: PointerEvent): void => {
+    onPointerUp = () => {
         SnappingManager.SetIsResizing(undefined);
         SnappingManager.clearSnapLines();
 
-        // Check if any outpainting needs to be processed
-        DocumentView.Selected()
-            .filter(dv => e.shiftKey && dv.ComponentView instanceof ImageBox)
-            .forEach(view => (view.ComponentView as ImageBox).processOutpainting());
-
         this._resizeHdlId = '';
         this._resizeUndo?.end();
 
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 9fc20ffd4..ac1a6ece9 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -106,7 +106,7 @@
     height: 100%;
     img {
         object-fit: contain;
-        height: 100%;
+        height: fit-content;
     }
 
     .imageBox-fadeBlocker,
@@ -249,27 +249,29 @@
     background: white;
     padding: 20px;
     border-radius: 8px;
-    box-shadow: 0 4px 12px rgba(0,0,0,0.2);
+    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
     z-index: 10000;
-  
-    h3 { margin-top: 0; }
-    
+
+    h3 {
+        margin-top: 0;
+    }
+
     input {
-      width: 300px;
-      padding: 8px;
-      margin-bottom: 10px;
+        width: 300px;
+        padding: 8px;
+        margin-bottom: 10px;
     }
-  
+
     .buttons {
-      display: flex;
-      justify-content: flex-end;
-      gap: 10px;
-  
-      .generate-btn {
-        background: #0078d4;
-        color: white;
-        border: none;
-        padding: 8px 16px;
-      }
+        display: flex;
+        justify-content: flex-end;
+        gap: 10px;
+
+        .generate-btn {
+            background: #0078d4;
+            color: white;
+            border: none;
+            padding: 8px 16px;
+        }
     }
-  }
\ No newline at end of file
+}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 010028af7..31a135fa7 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -170,6 +170,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             },
             { fireImmediately: true }
         );
+        this._disposers.outpaint = reaction(
+            () => this.Document[this.fieldKey + '_outpaintOriginalWidth'] !== undefined && !SnappingManager.ShiftKey,
+            complete => complete && this.openOutpaintPrompt(),
+            { fireImmediately: true }
+        );
     }
 
     componentWillUnmount() {
@@ -372,13 +377,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         const field = Cast(this.dataDoc[this.fieldKey], ImageField);
         if (!field) return;
 
-        const origWidth = NumCast(this.Document._outpaintingOriginalWidth);
-        const origHeight = NumCast(this.Document._outpaintingOriginalHeight);
-
-        if (!origWidth || !origHeight) {
-            console.error('Original dimensions (_outpaintingOriginalWidth/_outpaintingOriginalHeight) not set. Ensure resizeViewForOutpainting was called first.');
-            return;
-        }
+        const origWidth = NumCast(this.Document[this.fieldKey + '_outpaintOriginalWidth']);
+        const origHeight = NumCast(this.Document[this.fieldKey + '_outpaintOriginalHeight']);
 
         // Set flag that outpainting is in progress
         this._outpaintingInProgress = true;
@@ -444,8 +444,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                     this.Document.$ai = true;
                     this.Document.$ai_outpainted = true;
                     this.Document.$ai_outpaint_prompt = customPrompt;
-                    this.Document._outpaintingOriginalWidth = undefined;
-                    this.Document._outpaintingOriginalHeight = undefined;
+                    this.Document[this.fieldKey + '_outpaintOriginalWidth'] = undefined;
+                    this.Document[this.fieldKey + '_outpaintOriginalHeight'] = undefined;
                 } else {
                     this.Document._width = origWidth;
                     this.Document._height = origHeight;
@@ -465,11 +465,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         }
     };
 
-    processOutpainting = () => this.openOutpaintPrompt();
-
     componentUI = () =>
         !this._showOutpaintPrompt ? null : (
-            <div className="imageBox-regenerate-dialog" style={{ backgroundColor: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
+            <div key="imageBox-componentui" className="imageBox-regenerate-dialog" style={{ backgroundColor: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
                 <h3>Outpaint Image</h3>
                 <EditableText
                     placeholder="Enter a prompt for extending the image:"
@@ -726,7 +724,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                         ref={action((r: HTMLImageElement | null) => (this.imageRef = r))}
                         key="paths"
                         src={srcpath}
-                        style={{ transform, transformOrigin, height: 'fit-content' }}
+                        style={{ transform, transformOrigin, height: this.Document[this.fieldKey + '_outpaintOriginalWidth'] !== undefined ? '100%' : undefined }}
                         onError={action(e => (this._error = e.toString()))}
                         draggable={false}
                         width={nativeWidth}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 98e461a52..d6fa3172d 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1349,7 +1349,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     };
     doPaste = (view: EditorView, data: DataTransfer | null) => {
         const html = data?.getData('text/html');
-        const text = data?.getData('text/plain');
         const pdfAnchorId = data?.getData('dash/pdfAnchor');
         if (html && !pdfAnchorId) {
             const replaceDivsWithParagraphs = (expr: string) => {
diff --git a/src/client/views/nodes/imageEditor/imageMeshTool/imageMeshToolButton.tsx b/src/client/views/nodes/imageEditor/imageMeshTool/imageMeshToolButton.tsx
index c02a1eb94..e580c7070 100644
--- a/src/client/views/nodes/imageEditor/imageMeshTool/imageMeshToolButton.tsx
+++ b/src/client/views/nodes/imageEditor/imageMeshTool/imageMeshToolButton.tsx
@@ -13,20 +13,11 @@ interface ButtonContainerProps {
     btnText: string;
     imageWidth: number;
     imageHeight: number;
-    gridXSize: number;  // X subdivisions
-    gridYSize: number;  // Y subdivisions
+    gridXSize: number; // X subdivisions
+    gridYSize: number; // Y subdivisions
 }
 
-export function MeshTransformButton({
-    loading,
-    onClick,
-    onReset,
-    btnText,
-    imageWidth,
-    imageHeight,
-    gridXSize,
-    gridYSize
-}: ButtonContainerProps) {
+export function MeshTransformButton({ loading, onClick, onReset, btnText, imageWidth, imageHeight, gridXSize, gridYSize }: ButtonContainerProps) {
     const [showGrid, setShowGrid] = React.useState(false);
     const [isGridInteractive, setIsGridInteractive] = React.useState(false); // Controls the dragging of control points
     const imageRef = React.useRef<HTMLImageElement>(null); // Reference to the image element
-- 
cgit v1.2.3-70-g09d2

