From 3f54517e96ccff233b1560627995024e137dbdfd Mon Sep 17 00:00:00 2001
From: sharkiecodes <lanyi_stroud@brown.edu>
Date: Tue, 11 Mar 2025 16:27:30 -0400
Subject: Doing outpainting implementation

---
 src/client/views/nodes/ImageBox.tsx | 409 ++++++++++++++++++++++++++++--------
 1 file changed, 326 insertions(+), 83 deletions(-)

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

diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 5b06e9fc5..114d5226b 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,6 +1,7 @@
 import { Button, Colors, Size, Type } from '@dash/components';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Slider, Tooltip } from '@mui/material';
+import { DimensionField } from '../../../fields/DimensionField';
 import axios from 'axios';
 import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction } from 'mobx';
 import { observer } from 'mobx-react';
@@ -46,6 +47,7 @@ import { FocusViewOptions } from './FocusViewOptions';
 import './ImageBox.scss';
 import { OpenWhere } from './OpenWhere';
 import { RichTextField } from '../../../fields/RichTextField';
+import { List } from '../../../fields/List';
 
 export class ImageEditorData {
     // eslint-disable-next-line no-use-before-define
@@ -103,6 +105,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     @observable private _regenerateLoading = false;
     @observable private _prevImgs: FireflyImageData[] = StrCast(this.Document.ai_firefly_history) ? JSON.parse(StrCast(this.Document.ai_firefly_history)) : [];
 
+    // Add these observable properties to the ImageBox class
+    @observable private _outpaintingInProgress = false;
+    @observable private _outpaintingPrompt = '';
+
     constructor(props: FieldViewProps) {
         super(props);
         makeObservable(this);
@@ -135,6 +141,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     };
 
     componentDidMount() {
+        super.componentDidMount?.();
         this._disposers.sizer = reaction(
             () => ({
                 forceFull: this._props.renderDepth < 1 || this.layoutDoc._showFullRes,
@@ -148,9 +155,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         );
         const { layoutDoc } = this;
         this._disposers.path = reaction(
-            () => ({ nativeSize: this.nativeSize, width: NumCast(this.layoutDoc._width), height: this.layoutDoc._height }),
-            ({ nativeSize, width, height }) => {
-                if ((layoutDoc === this.layoutDoc && !this.layoutDoc._layout_nativeDimEditable) || !height) {
+            () => ({ nativeSize: this.nativeSize, width: NumCast(this.layoutDoc._width) }),
+            ({ nativeSize, width }) => {
+                if ((layoutDoc === this.layoutDoc && !this.layoutDoc._layout_nativeDimEditable) || !this.layoutDoc._height) {
                     this.layoutDoc._height = (width * nativeSize.nativeHeight) / nativeSize.nativeWidth;
                 }
             },
@@ -166,6 +173,14 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             },
             { fireImmediately: true }
         );
+        this._disposers.outpainting = reaction(
+            () => this.Document?._needsOutpainting,
+            needsOutpainting => {
+                if (needsOutpainting && this.Document?._outpaintingResize) {
+                    this.processOutpainting();
+                }
+            }
+        );
     }
 
     componentWillUnmount() {
@@ -214,7 +229,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                     }, true);
                 } else if (hitDropTarget(e.target as HTMLElement, this._regenerateIconRef.current)) {
                     this._regenerateLoading = true;
-                    const drag = de.complete.docDragData.draggedDocuments.lastElement();
+                    const drag = de.complete.docDragData?.draggedDocuments.lastElement();
                     const dragField = drag[Doc.LayoutFieldKey(drag)];
                     const oldPrompt = StrCast(this.Document.ai_firefly_prompt, StrCast(this.Document.title));
                     const newPrompt = (text: string) => (oldPrompt ? `${oldPrompt} ~~~ ${text}` : text);
@@ -339,6 +354,202 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         }
     });
 
+    // Add this method to process outpainting when resize is complete
+    @action
+    processOutpainting = async () => {
+        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;
+        }
+    
+        //alert(`Original dimensions: ${origWidth} x ${origHeight}`);
+    
+        
+        // Set flag that outpainting is in progress
+        this._outpaintingInProgress = true;
+        
+        try {
+            // Get the current path to the image
+            const currentPath = this.choosePath(field.url);
+            
+            // Get original and new dimensions for calculating mask
+            const newWidth = NumCast(this.Document._width);
+            const newHeight = NumCast(this.Document._height);
+            
+            // Optional: Ask user for a prompt to guide the outpainting
+            let prompt = "Extend this image naturally with matching content";
+            const customPrompt = await new Promise<string>((resolve) => {
+                const dialog = document.createElement('div');
+                Object.assign(dialog.style, {
+                    position: 'fixed',
+                    top: '50%',
+                    left: '50%',
+                    transform: 'translate(-50%, -50%)',
+                    background: 'white',
+                    padding: '20px',
+                    borderRadius: '8px',
+                    boxShadow: '0 4px 12px rgba(0,0,0,0.2)',
+                    zIndex: '10000'
+                });
+            
+                const title = document.createElement('h3');
+                title.style.marginTop = '0';
+                title.textContent = 'Outpaint Image';
+            
+                const description = document.createElement('p');
+                description.textContent = 'Enter a prompt for extending the image:';
+            
+                const input = document.createElement('input');
+                input.id = 'outpaint-prompt';
+                input.type = 'text';
+                input.value = 'Extend this image naturally with matching content';
+                Object.assign(input.style, {
+                    width: '300px',
+                    padding: '8px',
+                    marginBottom: '10px'
+                });
+            
+                const buttonContainer = document.createElement('div');
+                Object.assign(buttonContainer.style, {
+                    display: 'flex',
+                    justifyContent: 'flex-end',
+                    gap: '10px'
+                });
+            
+                const cancelButton = document.createElement('button');
+                cancelButton.textContent = 'Cancel';
+            
+                const confirmButton = document.createElement('button');
+                confirmButton.textContent = 'Generate';
+                Object.assign(confirmButton.style, {
+                    background: '#0078d4',
+                    color: 'white',
+                    border: 'none',
+                    padding: '8px 16px'
+                });
+            
+                buttonContainer.appendChild(cancelButton);
+                buttonContainer.appendChild(confirmButton);
+            
+                dialog.appendChild(title);
+                dialog.appendChild(description);
+                dialog.appendChild(input);
+                dialog.appendChild(buttonContainer);
+            
+                document.body.appendChild(dialog);
+            
+                cancelButton.onclick = () => {
+                    document.body.removeChild(dialog);
+                    resolve("");
+                };
+            
+                confirmButton.onclick = () => {
+                    const promptValue = input.value;
+                    document.body.removeChild(dialog);
+                    resolve(promptValue);
+                };
+            });
+            
+            // If user cancelled, reset dimensions to original
+            if (!customPrompt) {
+                this.Document._width = origWidth;
+                this.Document._height = origHeight;
+                this._outpaintingInProgress = false;
+                return;
+            }
+            
+            // Show loading indicator
+            const loadingOverlay = document.createElement('div');
+            loadingOverlay.style.position = 'absolute';
+            loadingOverlay.style.top = '0';
+            loadingOverlay.style.left = '0';
+            loadingOverlay.style.width = '100%';
+            loadingOverlay.style.height = '100%';
+            loadingOverlay.style.background = 'rgba(0,0,0,0.5)';
+            loadingOverlay.style.display = 'flex';
+            loadingOverlay.style.justifyContent = 'center';
+            loadingOverlay.style.alignItems = 'center';
+            loadingOverlay.innerHTML = '<div style="color: white; font-size: 16px;">Generating outpainted image...</div>';
+            this._mainCont?.appendChild(loadingOverlay);
+            
+            // Call the outpaint API
+            const response = await Networking.PostToServer('/outpaintImageFour', {
+                imageUrl: currentPath,
+                prompt: customPrompt,
+                originalDimensions: {
+                    width: origWidth,
+                    height: origHeight
+                },
+                newDimensions: {
+                    width: newWidth,
+                    height: newHeight
+                }
+            });
+            if (response && typeof response === 'object' && 'url' in response && typeof response.url === 'string') {
+                console.log("Response is valid and contains URL:", response.url);
+           
+            } else {
+                console.error("Unexpected API response:", response);
+                alert("Failed to receive a valid image URL from server.");
+            }
+            
+            
+            if (response && 'url' in response && typeof response.url === 'string') {
+                // Save the original image as an alternate
+                if (!this.dataDoc[this.fieldKey + '_alternates']) {
+                    this.dataDoc[this.fieldKey + '_alternates'] = new List<Doc>();
+                }
+                
+                // Create a copy of the current image as an alternate
+                const originalDoc = Docs.Create.ImageDocument(field.url.href, {
+                    title: `Original: ${this.Document.title}`,
+                    _nativeWidth: Doc.NativeWidth(this.dataDoc),
+                    _nativeHeight: Doc.NativeHeight(this.dataDoc)
+                });
+                
+                // Add to alternates
+                Doc.AddDocToList(this.dataDoc, this.fieldKey + '_alternates', originalDoc);
+                
+                // Update the image with the outpainted version
+                this.dataDoc[this.fieldKey] = new ImageField(response.url);
+                
+                // Update native dimensions
+                Doc.SetNativeWidth(this.dataDoc, newWidth);
+                Doc.SetNativeHeight(this.dataDoc, newHeight);
+                
+                // Add AI metadata
+                this.Document.ai = true;
+                this.Document.ai_outpainted = true;
+                this.Document.ai_outpaint_prompt = customPrompt;
+            } else {
+                // If failed, revert to original dimensions
+                this.Document._width = origWidth;
+                this.Document._height = origHeight;
+                alert("Failed to outpaint image. Please try again.");
+            }
+            
+            // Remove loading overlay
+            this._mainCont?.removeChild(loadingOverlay);
+            
+        } catch (error) {
+            console.error("Error during outpainting:", error);
+            // Revert to original dimensions on error
+            this.Document._width = origWidth
+            this.Document._height = origHeight
+            alert("An error occurred while outpainting. Please try again.");
+        } finally {
+            // Clear the outpainting flags
+            this._outpaintingInProgress = false;
+            delete this.Document._originalDims;
+        }
+    };
+
     specificContextMenu = (): void => {
         const field = Cast(this.dataDoc[this.fieldKey], ImageField);
         if (field) {
@@ -396,6 +607,27 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 event: action(undoable(async () => await StickerPalette.addToPalette(this.Document), 'save to palette')),
                 icon: this.Document.savedAsSticker ? 'clipboard-check' : 'file-arrow-down',
             });
+            // Add new outpainting option
+            funcs.push({ 
+                description: 'Outpaint Image', 
+                event: () => {
+                    this.processOutpainting();
+                }, 
+                icon: 'brush' 
+            });
+            // Add outpainting history option if the image was outpainted
+            this.Document.ai_outpainted &&
+                funcs.push({
+                    description: 'View Original Image',
+                    event: action(() => {
+                        const alternates = DocListCast(this.dataDoc[this.fieldKey + '_alternates']);
+                        if (alternates && alternates.length) {
+                            // Toggle to show the original image
+                            this.layoutDoc[this.fieldKey + '_usePath'] = 'alternate';
+                        }
+                    }),
+                    icon: 'image',
+                });
             ContextMenu.Instance?.addItem({ description: 'Options...', subitems: funcs, icon: 'asterisk' });
         }
     };
@@ -434,7 +666,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         const ext = extname(url.href);
         return url.href.replace(ext, (this._error ? '_o' : this._curSuffix) + ext);
     };
-    getScrollHeight = () => (this._props.fitWidth?.(this.Document) !== false && NumCast(this.layoutDoc._freeform_scale, 1) === NumCast(this.dataDoc._freeform_scaleMin, 1) ? this.nativeSize.nativeHeight : undefined);
+    getScrollHeight = () => (this._props.fitWidth?.(this.Document) !== false && NumCast(this.layoutDoc._freeform_scale, 1) === NumCast(this.dataDoc.freeform_scaleMin, 1) ? this.nativeSize.nativeHeight : undefined);
 
     @computed get usingAlternate() {
         const usePath = StrCast(this.Document[this.fieldKey + '_usePath']);
@@ -587,7 +819,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                     />
                     {fadepath === srcpath ? null : (
                         <div className={`imageBox-fadeBlocker${this.usingAlternate ? '-hover' : ''}`} style={{ transition: StrCast(this.layoutDoc.viewTransition, 'opacity 1000ms') }}>
-                            <img alt="" className="imageBox-fadeaway" key="fadeaway" src={fadepath} style={{ transform, transformOrigin }} draggable={false} width={nativeWidth} />
+                            <img alt="" className="imageBox-fadeaway" key="fadeaway" src={fadepath} style={{ transform, transformOrigin }} draggable={false} width={20} />
                         </div>
                     )}
                 </div>
@@ -746,82 +978,89 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         const alts = DocListCast(this.dataDoc[this.fieldKey + '_alternates']);
         const doc = this.usingAlternate ? (alts.lastElement() ?? this.Document) : this.Document;
         return (
-            <div
-                className="imageBox"
-                onContextMenu={this.specificContextMenu}
-                ref={this.createDropTarget}
-                onScroll={action(() => {
-                    if (!this._forcedScroll) {
-                        if (this.layoutDoc._layout_scrollTop || this._mainCont?.scrollTop) {
-                            this._ignoreScroll = true;
-                            this.layoutDoc._layout_scrollTop = this._mainCont?.scrollTop;
-                            this._ignoreScroll = false;
+            <>
+                <div
+                    className="imageBox"
+                    onContextMenu={this.specificContextMenu}
+                    ref={this.createDropTarget}
+                    onScroll={action(() => {
+                        if (!this._forcedScroll) {
+                            if (this.layoutDoc._layout_scrollTop || this._mainCont?.scrollTop) {
+                                this._ignoreScroll = true;
+                                this.layoutDoc._layout_scrollTop = this._mainCont?.scrollTop;
+                                this._ignoreScroll = false;
+                            }
                         }
-                    }
-                })}
-                style={{
-                    width: this._props.PanelWidth() ? undefined : `100%`,
-                    height: this._props.PanelHeight() ? undefined : `100%`,
-                    pointerEvents: this.layoutDoc._lockedPosition ? 'none' : undefined,
-                    borderRadius,
-                    overflow: this.layoutDoc.layout_fitWidth || this._props.fitWidth?.(this.Document) ? 'auto' : 'hidden',
-                }}>
-                <CollectionFreeFormView
-                    ref={this._ffref}
-                    {...this._props}
-                    Document={doc}
-                    setContentViewBox={emptyFunction}
-                    NativeWidth={returnZero}
-                    NativeHeight={returnZero}
-                    renderDepth={this._props.renderDepth + 1}
-                    fieldKey={this.annotationKey}
-                    styleProvider={this._props.styleProvider}
-                    isAnnotationOverlay
-                    annotationLayerHostsContent
-                    PanelWidth={this._props.PanelWidth}
-                    PanelHeight={this._props.PanelHeight}
-                    ScreenToLocalTransform={this.screenToLocalTransform}
-                    select={emptyFunction}
-                    focus={this.focus}
-                    getScrollHeight={this.getScrollHeight}
-                    NativeDimScaling={returnOne}
-                    isAnyChildContentActive={returnFalse}
-                    whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
-                    removeDocument={this.removeDocument}
-                    moveDocument={this.moveDocument}
-                    addDocument={this.addDocument}>
-                    {this.content}
-                </CollectionFreeFormView>
-                {this.Loading ? (
-                    <div className="loading-spinner" style={{ position: 'absolute' }}>
-                        <ReactLoading type="spin" height={50} width={50} color={'blue'} />
+                    })}
+                    style={{
+                        width: this._props.PanelWidth() ? undefined : `100%`,
+                        height: this._props.PanelHeight() ? undefined : `100%`,
+                        pointerEvents: this.layoutDoc._lockedPosition ? 'none' : undefined,
+                        borderRadius,
+                        overflow: this.layoutDoc.layout_fitWidth || this._props.fitWidth?.(this.Document) ? 'auto' : 'hidden',
+                    }}>
+                    <CollectionFreeFormView
+                        ref={this._ffref}
+                        {...this._props}
+                        Document={doc}
+                        setContentViewBox={emptyFunction}
+                        NativeWidth={returnZero}
+                        NativeHeight={returnZero}
+                        renderDepth={this._props.renderDepth + 1}
+                        fieldKey={this.annotationKey}
+                        styleProvider={this._props.styleProvider}
+                        isAnnotationOverlay
+                        annotationLayerHostsContent
+                        PanelWidth={this._props.PanelWidth}
+                        PanelHeight={this._props.PanelHeight}
+                        ScreenToLocalTransform={this.screenToLocalTransform}
+                        select={emptyFunction}
+                        focus={this.focus}
+                        getScrollHeight={this.getScrollHeight}
+                        NativeDimScaling={returnOne}
+                        isAnyChildContentActive={returnFalse}
+                        whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
+                        removeDocument={this.removeDocument}
+                        moveDocument={this.moveDocument}
+                        addDocument={this.addDocument}>
+                        {this.content}
+                    </CollectionFreeFormView>
+                    {this.Loading ? (
+                        <div className="loading-spinner" style={{ position: 'absolute' }}>
+                            <ReactLoading type="spin" height={50} width={50} color={'blue'} />
+                        </div>
+                    ) : null}
+                    {this.regenerateImageIcon}
+                    {this.overlayImageIcon}
+                    {this.annotationLayer}
+                    {!this._mainCont || !this.DocumentView || !this._annotationLayer.current ? null : (
+                        <MarqueeAnnotator
+                            Document={this.Document}
+                            ref={this.marqueeref}
+                            scrollTop={0}
+                            annotationLayerScrollTop={0}
+                            scaling={returnOne}
+                            annotationLayerScaling={this._props.NativeDimScaling}
+                            screenTransform={this.DocumentView().screenToViewTransform}
+                            docView={this.DocumentView}
+                            addDocument={this.addDocument}
+                            finishMarquee={this.finishMarquee}
+                            savedAnnotations={this.savedAnnotations}
+                            selectionText={returnEmptyString}
+                            annotationLayer={this._annotationLayer.current}
+                            marqueeContainer={this._mainCont}
+                            highlightDragSrcColor=""
+                            anchorMenuCrop={this.crop}
+                            // anchorMenuFlashcard={() => this.getImageDesc()}
+                        />
+                    )}
+                </div>
+                {this._outpaintingInProgress && (
+                    <div className="imageBox-outpaintingSpinner">
+                        <ReactLoading type="spin" color="#666" height={60} width={60} />
                     </div>
-                ) : null}
-                {this.regenerateImageIcon}
-                {this.overlayImageIcon}
-                {this.annotationLayer}
-                {!this._mainCont || !this.DocumentView || !this._annotationLayer.current ? null : (
-                    <MarqueeAnnotator
-                        Document={this.Document}
-                        ref={this.marqueeref}
-                        scrollTop={0}
-                        annotationLayerScrollTop={0}
-                        scaling={returnOne}
-                        annotationLayerScaling={this._props.NativeDimScaling}
-                        screenTransform={this.DocumentView().screenToViewTransform}
-                        docView={this.DocumentView}
-                        addDocument={this.addDocument}
-                        finishMarquee={this.finishMarquee}
-                        savedAnnotations={this.savedAnnotations}
-                        selectionText={returnEmptyString}
-                        annotationLayer={this._annotationLayer.current}
-                        marqueeContainer={this._mainCont}
-                        highlightDragSrcColor=""
-                        anchorMenuCrop={this.crop}
-                        // anchorMenuFlashcard={() => this.getImageDesc()}
-                    />
                 )}
-            </div>
+            </>
         );
     }
 
@@ -834,10 +1073,14 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             const file = input.files?.[0];
             if (file) {
                 const disposer = OverlayView.ShowSpinner();
-                DocUtils.uploadFileToDoc(file, {}, this.Document).then(doc => {
-                    disposer();
-                    doc && (doc.height = undefined);
-                });
+                const [{ result }] = await Networking.UploadFilesToServer({ file });
+                if (result instanceof Error) {
+                    alert('Error uploading files - possibly due to unsupported file types');
+                } else {
+                    this.dataDoc[this.fieldKey] = new ImageField(result.accessPaths.agnostic.client);
+                    !(result instanceof Error) && DocUtils.assignImageInfo(result, this.dataDoc);
+                }
+                disposer();
             } else {
                 console.log('No file selected');
             }
-- 
cgit v1.2.3-70-g09d2


From 79f44d95eb789d7b3cca33eedc38072f2bb43d2a Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 11 Mar 2025 18:15:34 -0400
Subject: fixed calling firefly api for expand image

---
 src/client/views/nodes/ImageBox.tsx               | 111 ++++---
 src/client/views/smartdraw/DrawingFillHandler.tsx |   1 +
 src/server/ApiManagers/FireflyManager.ts          | 363 ++++------------------
 3 files changed, 118 insertions(+), 357 deletions(-)

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

diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 114d5226b..a7dd5de15 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,7 +1,6 @@
 import { Button, Colors, Size, Type } from '@dash/components';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Slider, Tooltip } from '@mui/material';
-import { DimensionField } from '../../../fields/DimensionField';
 import axios from 'axios';
 import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction } from 'mobx';
 import { observer } from 'mobx-react';
@@ -359,32 +358,31 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     processOutpainting = async () => {
         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.");
+            console.error('Original dimensions (_outpaintingOriginalWidth/_outpaintingOriginalHeight) not set. Ensure resizeViewForOutpainting was called first.');
             return;
         }
-    
+
         //alert(`Original dimensions: ${origWidth} x ${origHeight}`);
-    
-        
+
         // Set flag that outpainting is in progress
         this._outpaintingInProgress = true;
-        
+
         try {
             // Get the current path to the image
             const currentPath = this.choosePath(field.url);
-            
+
             // Get original and new dimensions for calculating mask
             const newWidth = NumCast(this.Document._width);
             const newHeight = NumCast(this.Document._height);
-            
+
             // Optional: Ask user for a prompt to guide the outpainting
-            let prompt = "Extend this image naturally with matching content";
-            const customPrompt = await new Promise<string>((resolve) => {
+            let prompt = 'Extend this image naturally with matching content';
+            const customPrompt = await new Promise<string>(resolve => {
                 const dialog = document.createElement('div');
                 Object.assign(dialog.style, {
                     position: 'fixed',
@@ -395,16 +393,16 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                     padding: '20px',
                     borderRadius: '8px',
                     boxShadow: '0 4px 12px rgba(0,0,0,0.2)',
-                    zIndex: '10000'
+                    zIndex: '10000',
                 });
-            
+
                 const title = document.createElement('h3');
                 title.style.marginTop = '0';
                 title.textContent = 'Outpaint Image';
-            
+
                 const description = document.createElement('p');
                 description.textContent = 'Enter a prompt for extending the image:';
-            
+
                 const input = document.createElement('input');
                 input.id = 'outpaint-prompt';
                 input.type = 'text';
@@ -412,50 +410,50 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 Object.assign(input.style, {
                     width: '300px',
                     padding: '8px',
-                    marginBottom: '10px'
+                    marginBottom: '10px',
                 });
-            
+
                 const buttonContainer = document.createElement('div');
                 Object.assign(buttonContainer.style, {
                     display: 'flex',
                     justifyContent: 'flex-end',
-                    gap: '10px'
+                    gap: '10px',
                 });
-            
+
                 const cancelButton = document.createElement('button');
                 cancelButton.textContent = 'Cancel';
-            
+
                 const confirmButton = document.createElement('button');
                 confirmButton.textContent = 'Generate';
                 Object.assign(confirmButton.style, {
                     background: '#0078d4',
                     color: 'white',
                     border: 'none',
-                    padding: '8px 16px'
+                    padding: '8px 16px',
                 });
-            
+
                 buttonContainer.appendChild(cancelButton);
                 buttonContainer.appendChild(confirmButton);
-            
+
                 dialog.appendChild(title);
                 dialog.appendChild(description);
                 dialog.appendChild(input);
                 dialog.appendChild(buttonContainer);
-            
+
                 document.body.appendChild(dialog);
-            
+
                 cancelButton.onclick = () => {
                     document.body.removeChild(dialog);
-                    resolve("");
+                    resolve('');
                 };
-            
+
                 confirmButton.onclick = () => {
                     const promptValue = input.value;
                     document.body.removeChild(dialog);
                     resolve(promptValue);
                 };
             });
-            
+
             // If user cancelled, reset dimensions to original
             if (!customPrompt) {
                 this.Document._width = origWidth;
@@ -463,7 +461,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 this._outpaintingInProgress = false;
                 return;
             }
-            
+
             // Show loading indicator
             const loadingOverlay = document.createElement('div');
             loadingOverlay.style.position = 'absolute';
@@ -477,52 +475,50 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             loadingOverlay.style.alignItems = 'center';
             loadingOverlay.innerHTML = '<div style="color: white; font-size: 16px;">Generating outpainted image...</div>';
             this._mainCont?.appendChild(loadingOverlay);
-            
+
             // Call the outpaint API
-            const response = await Networking.PostToServer('/outpaintImageFour', {
+            const response = await Networking.PostToServer('/outpaintImage', {
                 imageUrl: currentPath,
                 prompt: customPrompt,
                 originalDimensions: {
                     width: origWidth,
-                    height: origHeight
+                    height: origHeight,
                 },
                 newDimensions: {
                     width: newWidth,
-                    height: newHeight
-                }
+                    height: newHeight,
+                },
             });
             if (response && typeof response === 'object' && 'url' in response && typeof response.url === 'string') {
-                console.log("Response is valid and contains URL:", response.url);
-           
+                console.log('Response is valid and contains URL:', response.url);
             } else {
-                console.error("Unexpected API response:", response);
-                alert("Failed to receive a valid image URL from server.");
+                console.error('Unexpected API response:', response);
+                alert('Failed to receive a valid image URL from server.');
             }
-            
-            
+
             if (response && 'url' in response && typeof response.url === 'string') {
                 // Save the original image as an alternate
                 if (!this.dataDoc[this.fieldKey + '_alternates']) {
                     this.dataDoc[this.fieldKey + '_alternates'] = new List<Doc>();
                 }
-                
+
                 // Create a copy of the current image as an alternate
                 const originalDoc = Docs.Create.ImageDocument(field.url.href, {
                     title: `Original: ${this.Document.title}`,
                     _nativeWidth: Doc.NativeWidth(this.dataDoc),
-                    _nativeHeight: Doc.NativeHeight(this.dataDoc)
+                    _nativeHeight: Doc.NativeHeight(this.dataDoc),
                 });
-                
+
                 // Add to alternates
                 Doc.AddDocToList(this.dataDoc, this.fieldKey + '_alternates', originalDoc);
-                
+
                 // Update the image with the outpainted version
                 this.dataDoc[this.fieldKey] = new ImageField(response.url);
-                
+
                 // Update native dimensions
                 Doc.SetNativeWidth(this.dataDoc, newWidth);
                 Doc.SetNativeHeight(this.dataDoc, newHeight);
-                
+
                 // Add AI metadata
                 this.Document.ai = true;
                 this.Document.ai_outpainted = true;
@@ -531,18 +527,17 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 // If failed, revert to original dimensions
                 this.Document._width = origWidth;
                 this.Document._height = origHeight;
-                alert("Failed to outpaint image. Please try again.");
+                alert('Failed to outpaint image. Please try again.');
             }
-            
+
             // Remove loading overlay
             this._mainCont?.removeChild(loadingOverlay);
-            
         } catch (error) {
-            console.error("Error during outpainting:", error);
+            console.error('Error during outpainting:', error);
             // Revert to original dimensions on error
-            this.Document._width = origWidth
-            this.Document._height = origHeight
-            alert("An error occurred while outpainting. Please try again.");
+            this.Document._width = origWidth;
+            this.Document._height = origHeight;
+            alert('An error occurred while outpainting. Please try again.');
         } finally {
             // Clear the outpainting flags
             this._outpaintingInProgress = false;
@@ -608,12 +603,12 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 icon: this.Document.savedAsSticker ? 'clipboard-check' : 'file-arrow-down',
             });
             // Add new outpainting option
-            funcs.push({ 
-                description: 'Outpaint Image', 
+            funcs.push({
+                description: 'Outpaint Image',
                 event: () => {
                     this.processOutpainting();
-                }, 
-                icon: 'brush' 
+                },
+                icon: 'brush',
             });
             // Add outpainting history option if the image was outpainted
             this.Document.ai_outpainted &&
diff --git a/src/client/views/smartdraw/DrawingFillHandler.tsx b/src/client/views/smartdraw/DrawingFillHandler.tsx
index c672bc718..37799a98d 100644
--- a/src/client/views/smartdraw/DrawingFillHandler.tsx
+++ b/src/client/views/smartdraw/DrawingFillHandler.tsx
@@ -48,6 +48,7 @@ export class DrawingFillHandler {
                             .then(res => {
                                 const genratedDocs = DocCast(drawing.ai_firefly_generatedDocs) ?? Docs.Create.MasonryDocument([], { _width: 400, _height: 400 });
                                 drawing[DocData].ai_firefly_generatedDocs = genratedDocs;
+                                if ('error' in res) throw new Error(res.error as string);
                                 (res as Upload.ImageInformation[]).map(info =>
                                     Doc.AddDocToList(
                                         genratedDocs,
diff --git a/src/server/ApiManagers/FireflyManager.ts b/src/server/ApiManagers/FireflyManager.ts
index 1e0fdb595..8b0fa6ec7 100644
--- a/src/server/ApiManagers/FireflyManager.ts
+++ b/src/server/ApiManagers/FireflyManager.ts
@@ -8,7 +8,7 @@ import { DashUploadUtils } from '../DashUploadUtils';
 import { _error, _invalid, _success, Method } from '../RouteManager';
 import { Directory, filesDirectory } from '../SocketData';
 import ApiManager, { Registration } from './ApiManager';
-import { Upload } from '../SharedMediaTypes'; 
+import { Upload } from '../SharedMediaTypes';
 
 export default class FireflyManager extends ApiManager {
     getBearerToken = () =>
@@ -332,315 +332,80 @@ export default class FireflyManager extends ApiManager {
 
         register({
             method: Method.POST,
-            subscription: '/outpaintImageFour',
+            subscription: '/outpaintImage',
             secureHandler: ({ req, res }) =>
-
-                this.uploadImageToDropbox(req.body.imageUrl, req.user as DashUserModel)
-                    .then(uploadUrl => {
-                        if (uploadUrl instanceof Error) {
-                            _invalid(res, uploadUrl.message);
-                            return;
-                        }
-                        return this.getBearerToken()
-                            .then(tokenResponse => tokenResponse?.json())
-                            .then((tokenData: { access_token: string }) =>
-                                fetch('https://firefly-api.adobe.io/v3/images/expand', {
-                                    method: 'POST',
-                                    headers: {
-                                        //'Content-Type': 'application/json',
-                                        'Accept': 'application/json',
-                                        'x-api-key': process.env._CLIENT_FIREFLY_CLIENT_ID ?? '',
-                                        'Authorization': `Bearer ${tokenData.access_token}`,
+                this.uploadImageToDropbox(req.body.imageUrl, req.user as DashUserModel).then(uploadUrl => {
+                    if (uploadUrl instanceof Error) {
+                        _invalid(res, uploadUrl.message);
+                        return;
+                    }
+                    return this.getBearerToken()
+                        .then(tokenResponse => tokenResponse?.json())
+                        .then((tokenData: { access_token: string }) =>
+                            fetch('https://firefly-api.adobe.io/v3/images/expand', {
+                                method: 'POST',
+                                headers: [
+                                    ['Content-Type', 'application/json'],
+                                    ['Accept', 'application/json'],
+                                    ['x-api-key', process.env._CLIENT_FIREFLY_CLIENT_ID ?? ''],
+                                    ['Authorization', `Bearer ${tokenData.access_token}`],
+                                ],
+                                body: JSON.stringify({
+                                    image: {
+                                        source: { url: uploadUrl },
                                     },
-                                    body: JSON.stringify({
-                                        image: {
-                                            source: { url: uploadUrl },
-                                        },
-                                        size: {
-                                            width: req.body.newDimensions.width,
-                                            height: req.body.newDimensions.height,
+                                    // mask: {
+                                    //     source: { url: uploadUrl },
+                                    // },
+                                    size: {
+                                        width: Math.round(req.body.newDimensions.width),
+                                        height: Math.round(req.body.newDimensions.height),
+                                    },
+                                    prompt: req.body.prompt ?? '',
+                                    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),
                                         },
-                                        prompt: req.body.prompt ?? '',
-                                        numVariations: 1,
-                                        placement: {
-                                            inset: {
-                                                left: 0,
-                                                top: 0,
-                                                right: req.body.newDimensions.width - req.body.originalDimensions.width,
-                                                bottom: req.body.newDimensions.height - req.body.originalDimensions.height,
-                                            },
-                                            alignment: {
-                                                horizontal: 'center',
-                                                vertical: 'center',
-                                            },
+                                        alignment: {
+                                            horizontal: 'center',
+                                            vertical: 'center',
                                         },
-                                    }),
-                                })
-                            )
-                            .then(expandResp => expandResp?.json())
-                            .then(expandData => {
-                                if (expandData.error_code || !expandData.outputs?.[0]?.image?.url) {
-                                    console.error('Firefly validation error:', expandData);
-                                    _error(res, expandData.message ?? 'Failed to generate image');
-                                } else {
-                                    DashUploadUtils.UploadImage(expandData.outputs[0].image.url)
-                                        .then((info: Upload.ImageInformation | Error) => {
-                                            if (info instanceof Error) {
-                                                _invalid(res, info.message);
-                                            } else {
-                                                _success(res, { url: info.accessPaths.agnostic.client });
-                                            }
-                                        })
-                                        .catch(uploadErr => {
-                                            console.error('DashUpload Error:', uploadErr);
-                                            _error(res, 'Failed to upload generated image.');
-                                        });
-                                }
+                                    },
+                                }),
                             })
-                            .catch(err => {
-                                console.error('Firefly request error:', err);
-                                _error(res, 'Failed to expand image');
-                            });
-                    }),
-        });
-        register({
-            method: Method.POST,
-            subscription: '/outpaintImageThree',
-            secureHandler: ({ req, res }) =>
-                new Promise<void>(resolver => {
-                    this.uploadImageToDropbox(req.body.imageUrl, req.user as DashUserModel)
-                        .then(dropboxImgUrl => {
-                            if (dropboxImgUrl instanceof Error) {
-                                _invalid(res, dropboxImgUrl.message);
-                                throw new Error('Error uploading image to dropbox');
+                        )
+                        .then(expandResp => expandResp?.json())
+                        .then(expandData => {
+                            if (expandData.error_code || !expandData.outputs?.[0]?.image?.url) {
+                                console.error('Firefly validation error:', expandData);
+                                _error(res, expandData.message ?? 'Failed to generate image');
+                            } else {
+                                return DashUploadUtils.UploadImage(expandData.outputs[0].image.url)
+                                    .then((info: Upload.ImageInformation | Error) => {
+                                        if (info instanceof Error) {
+                                            _invalid(res, info.message);
+                                        } else {
+                                            _success(res, { url: info.accessPaths.agnostic.client });
+                                        }
+                                    })
+                                    .catch(uploadErr => {
+                                        console.error('DashUpload Error:', uploadErr);
+                                        _error(res, 'Failed to upload generated image.');
+                                    });
                             }
-                            return dropboxImgUrl;
                         })
-                        .then(dropboxImgUrl =>
-                            this.getBearerToken().then(tokenResponse =>
-                                tokenResponse?.json().then((tokenData: { access_token: string }) =>
-                                    fetch('https://firefly-api.adobe.io/v3/images/expand', {
-                                        method: 'POST',
-                                        headers: [
-                                            ['Content-Type', 'application/json'],
-                                            ['Accept', 'application/json'],
-                                            ['x-api-key', process.env._CLIENT_FIREFLY_CLIENT_ID ?? ''],
-                                            ['Authorization', `Bearer ${tokenData.access_token}`],
-                                        ],
-                                        body: JSON.stringify({
-                                            image: {
-                                                source: {
-                                                    url: dropboxImgUrl,
-                                                },
-                                            },
-                                            numVariations: 1,
-                                            prompt: req.body.prompt,
-                                            size: {
-                                                width: req.body.newDimensions.width,
-                                                height: req.body.newDimensions.height,
-                                            },
-                                            placement: {
-                                                inset: {
-                                                    left: 0,
-                                                    top: 0,
-                                                    right: req.body.newDimensions.width - req.body.originalDimensions.width,
-                                                    bottom: req.body.newDimensions.height - req.body.originalDimensions.height,
-                                                },
-                                                alignment: {
-                                                    horizontal: 'center',
-                                                    vertical: 'center',
-                                                },
-                                            },
-                                        }),
-                                    })
-                                        .then(resp => resp.json())
-                                        .then(expandData => {
-                                            if (expandData.error_code || !expandData.outputs?.[0]?.image?.url) {
-                                                _invalid(res, expandData.message ?? 'Failed to expand image');
-                                                resolver();
-                                            } else {
-                                                DashUploadUtils.UploadImage(expandData.outputs[0].image.url)
-                                                    .then(info => {
-                                                        if (info instanceof Error) {
-                                                            _invalid(res, info.message);
-                                                        } else {
-                                                            _success(res, { url: info.accessPaths.agnostic.client });
-                                                        }
-                                                    })
-                                                    .then(resolver)
-                                                    .catch(uploadErr => {
-                                                        console.error('DashUpload Error:', uploadErr);
-                                                        _invalid(res, 'Failed to upload generated image.');
-                                                        resolver();
-                                                    });
-                                            }
-                                        })
-                                        .catch(err => {
-                                            console.error('Firefly API Error:', err);
-                                            _error(res, 'Failed to expand image');
-                                            resolver();
-                                        })
-                                )
-                            )
-                        )
                         .catch(err => {
-                            console.error('Dropbox Error:', err);
-                            _error(res, 'Failed to upload image to Dropbox');
-                            resolver();
+                            console.error('Firefly request error:', err);
+                            _error(res, 'Failed to expand image');
                         });
                 }),
         });
-        
-
-        register({
-            method: Method.POST,
-            subscription: '/outpaintImageTwo',
-            secureHandler: async ({ req, res }) => {
-              try {
-                const uploadUrl = await this.uploadImageToDropbox(req.body.imageUrl, req.user as DashUserModel);
-          
-                if (uploadUrl instanceof Error) {
-                  _invalid(res, uploadUrl.message);
-                  return;
-                }
-          
-                const tokenResponse = await this.getBearerToken();
-                const tokenData = await tokenResponse?.json();
-          
-                const body = JSON.stringify({
-                  image: { source: { url: uploadUrl } },
-                  prompt: req.body.prompt,
-                  numVariations: 1,
-                  size: {
-                    width: req.body.newDimensions.width,
-                    height: req.body.newDimensions.height,
-                  },
-                  placement: {
-                    inset: {
-                      left: 0,
-                      top: 0,
-                      right: req.body.newDimensions.width - req.body.originalDimensions.width,
-                      bottom: req.body.newDimensions.height - req.body.originalDimensions.height,
-                    },
-                    alignment: {
-                      horizontal: 'center',
-                      vertical: 'center',
-                    },
-                  },
-                });
-          
-                console.log('Sending outpainting request:', body);
-          
-                const expandResp = await fetch('https://firefly-api.adobe.io/v3/images/expand', {
-                  method: 'POST',
-                  headers: {
-                    'Content-Type': 'application/json',
-                    Accept: 'application/json',
-                    'x-api-key': process.env._CLIENT_FIREFLY_CLIENT_ID ?? '',
-                    Authorization: `Bearer ${tokenData.access_token}`,
-                  },
-                  body,
-                });
-          
-                const expandData = await expandResp.json();
-                console.log('Received expandData:', expandData);
-          
-                if (expandData.error_code || !expandData.outputs?.[0]?.image?.url) {
-                  console.error('Expand API Error:', expandData);
-                  _error(res, expandData.message ?? 'Failed to generate image');
-                  return;
-                }
-          
-                const uploadedInfo = await DashUploadUtils.UploadImage(expandData.outputs[0].image.url);
-          
-                if (uploadedInfo instanceof Error) {
-                  console.error('Upload Error:', uploadedInfo.message);
-                  _invalid(res, uploadedInfo.message);
-                  return;
-                }
-          
-                console.log('Successfully uploaded image URL:', uploadedInfo.accessPaths.agnostic.client);
-                _success(res, { url: uploadedInfo.accessPaths.agnostic.client });
-              } catch (err) {
-                console.error('Unexpected error during outpainting:', err);
-                _error(res, 'Unexpected error during outpainting');
-              }
-            },
-          });
-          
-        register({
-
-            
-            method: Method.POST,
-            subscription: '/outpaintImage',
-            secureHandler: ({ req, res }) =>
-                this.uploadImageToDropbox(req.body.imageUrl, req.user as DashUserModel).then(uploadUrl =>
-                    uploadUrl instanceof Error
-                        ? _invalid(res, uploadUrl.message)
-                        : this.getBearerToken()
-                            .then(tokenResponse => tokenResponse?.json())
-                            .then((tokenData: { access_token: string }) => 
-                                fetch('https://firefly-api.adobe.io/v3/images/expand', {
-                                    method: 'POST',
-                                    headers: [
-                                        ['Content-Type', 'application/json'],
-                                        ['Accept', 'application/json'],
-                                        ['x-api-key', process.env._CLIENT_FIREFLY_CLIENT_ID ?? ''],
-                                        ['Authorization', `Bearer ${tokenData.access_token}`],
-                                    ],
-                                    body: JSON.stringify({
-                                        image: { source: { url: uploadUrl } },
-                                        numVariations: 1,
-                                        size: {
-                                            width: req.body.newDimensions.width,
-                                            height: req.body.newDimensions.height,
-                                        },
-                                        prompt: req.body.prompt,
-                                        placement: {
-                                            inset: {
-                                                left: 0,
-                                                top: 0,
-                                                right: 0, //req.body.newDimensions.width - req.body.originalDimensions.width,
-                                                bottom: 0 //req.body.newDimensions.height - req.body.originalDimensions.height,
-                                            },
-                                            alignment: {
-                                                horizontal: 'center',
-                                                vertical: 'center',
-                                            },
-                                        },
-                                    }),
-                                })
-                            )
-                            .then(expandResp => expandResp.json())
-                            .then(expandData => {
-                                if (expandData.error_code || !expandData.outputs?.[0]?.image?.url) {
-                                    _error(res, expandData.message ?? "Failed to generate image");
-                                } else {
-                                    DashUploadUtils.UploadImage(expandData.outputs[0].image.url)
-                                        .then((info: Upload.ImageInformation | Error) => {
-                                            if (info instanceof Error) {
-                                                _invalid(res, info.message);
-                                            } else {
-                                                // THIS IS CRUCIAL: Respond exactly how your front end expects
-                                                console.log("Successfully uploaded image. URL:", info.accessPaths.agnostic.client);
-                                                _success(res, { url: info.accessPaths.agnostic.client });
-                                            }
-                                        })
-                                        .catch(uploadErr => {
-                                            
-                                            console.error('DashUpload Error:', uploadErr);
-                                            _error(res, 'Failed to upload generated image.');
-                                        });
-                                }
-                            })
-                            .catch(err => {
-                                console.error(err);
-                                _error(res, 'Failed to expand image');
-                            })
-                ),
-        });
 
-      /*  register({
+        /*  register({
             method: Method.POST
             subscription: '/queryFireflyOutpaint',
             secureHandler: ({req, res}) =>
@@ -683,7 +448,7 @@ export default class FireflyManager extends ApiManager {
                         : this.expandImage(uploadUrl, req.body.prompt).then(text => {
                               if (text.error_code) _error(res, text.message);
                               else
-                                  DashUploadUtils.UploadImage(text.outputs[0].image.url).then(info => {
+                                  return DashUploadUtils.UploadImage(text.outputs[0].image.url).then(info => {
                                       if (info instanceof Error) _invalid(res, info.message);
                                       else _success(res, info);
                                   });
-- 
cgit v1.2.3-70-g09d2


From d0b117d1f31f911d46dffe69e2f5c40b2bc5de3e Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 11 Mar 2025 23:08:16 -0400
Subject: getting rid of old expand image code

---
 src/client/views/nodes/ImageBox.tsx      | 24 ++----------------------
 src/server/ApiManagers/FireflyManager.ts | 17 -----------------
 2 files changed, 2 insertions(+), 39 deletions(-)

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

diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index a7dd5de15..14adfaf1e 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -552,9 +552,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             funcs.push({ description: 'Rotate Clockwise 90', event: this.rotate, icon: 'redo-alt' });
             funcs.push({ description: `Show ${this.layoutDoc._showFullRes ? 'Dynamic Res' : 'Full Res'}`, event: this.resolution, icon: 'expand' });
             funcs.push({ description: 'Set Native Pixel Size', event: this.setNativeSize, icon: 'expand-arrows-alt' });
-            funcs.push({
-                description: 'GetImageText',
-                event: () => {
+            funcs.push({ description: 'GetImageText',  event: () => {
                     Networking.PostToServer('/queryFireflyImageText', {
                         file: (file => {
                             const ext = extname(file);
@@ -563,25 +561,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                     }).then(text => alert(text));
                 },
                 icon: 'expand-arrows-alt',
-            });
-            funcs.push({
-                description: 'Expand Image',
-                event: () => {
-                    Networking.PostToServer('/expandImage', {
-                        prompt: 'sunny skies',
-                        file: (file => {
-                            const ext = extname(file);
-                            return file.replace(ext, (this._error ? '_o' : this._curSuffix) + ext);
-                        })(ImageCast(this.Document[Doc.LayoutFieldKey(this.Document)])?.url.href),
-                    }).then(res => {
-                        const info = res as Upload.ImageInformation;
-                        const img = Docs.Create.ImageDocument(info.accessPaths.agnostic.client, { title: 'expand:' + this.Document.title });
-                        DocUtils.assignImageInfo(info, img);
-                        this._props.addDocTab(img, OpenWhere.addRight);
-                    });
-                },
-                icon: 'expand-arrows-alt',
-            });
+            }); // prettier-ignore
             funcs.push({ description: 'Copy path', event: () => ClientUtils.CopyText(this.choosePath(field.url)), icon: 'copy' });
             funcs.push({ description: 'Open Image Editor', event: this.docEditorView, icon: 'pencil-alt' });
             this.layoutDoc.ai &&
diff --git a/src/server/ApiManagers/FireflyManager.ts b/src/server/ApiManagers/FireflyManager.ts
index 77655bb23..887c4aa72 100644
--- a/src/server/ApiManagers/FireflyManager.ts
+++ b/src/server/ApiManagers/FireflyManager.ts
@@ -429,23 +429,6 @@ export default class FireflyManager extends ApiManager {
                     )
                 ),
         });
-        register({
-            method: Method.POST,
-            subscription: '/expandImage',
-            secureHandler: ({ req, res }) =>
-                this.uploadImageToDropbox(req.body.file, req.user as DashUserModel).then(uploadUrl =>
-                    uploadUrl instanceof Error
-                        ? _invalid(res, uploadUrl.message)
-                        : this.expandImage(uploadUrl, req.body.prompt).then(text => {
-                              if (text.error_code) _error(res, text.message);
-                              else
-                                  return DashUploadUtils.UploadImage(text.outputs[0].image.url).then(info => {
-                                      if (info instanceof Error) _invalid(res, info.message);
-                                      else _success(res, info);
-                                  });
-                          })
-                ),
-        });
 
         // construct this url and send user to it.  It will allow them to authorize their dropbox account and will send the resulting token to our endpoint /refreshDropbox
         // https://www.dropbox.com/oauth2/authorize?client_id=DROPBOX_CLIENT_ID&response_type=code&token_access_type=offline&redirect_uri=http://localhost:1050/refreshDropbox
-- 
cgit v1.2.3-70-g09d2


From 22a40443193320487c27ce02bd3f134d13cb7d65 Mon Sep 17 00:00:00 2001
From: sharkiecodes <lanyi_stroud@brown.edu>
Date: Wed, 9 Apr 2025 23:58:46 -0400
Subject: cleaned up code

---
 src/client/views/nodes/ImageBox.scss |  32 +++
 src/client/views/nodes/ImageBox.tsx  | 494 ++++++++++++++++++++++-------------
 2 files changed, 351 insertions(+), 175 deletions(-)

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

diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 3d6942e6f..d7a14a9df 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -245,3 +245,35 @@
         color: black;
     }
 }
+.imageBox-regenerate-dialog {
+    position: fixed;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    background: white;
+    padding: 20px;
+    border-radius: 8px;
+    box-shadow: 0 4px 12px rgba(0,0,0,0.2);
+    z-index: 10000;
+  
+    h3 { margin-top: 0; }
+    
+    input {
+      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;
+      }
+    }
+  }
\ No newline at end of file
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 14adfaf1e..033b0b5a2 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -353,197 +353,340 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         }
     });
 
-    // Add this method to process outpainting when resize is complete
-    @action
-    processOutpainting = async () => {
-        const field = Cast(this.dataDoc[this.fieldKey], ImageField);
-        if (!field) return;
+    @observable _showOutpaintPrompt: boolean = false;
+@observable _outpaintPromptInput: string = 'Extend this image naturally with matching content';
+
+@action
+openOutpaintPrompt = () => {
+  this._showOutpaintPrompt = true;
+};
+
+@action
+closeOutpaintPrompt = () => {
+  this._showOutpaintPrompt = false;
+};
+
+@action
+handlePromptChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+  this._outpaintPromptInput = e.target.value;
+};
+
+@action
+submitOutpaintPrompt = () => {
+  this.closeOutpaintPrompt();
+  this.processOutpaintingWithPrompt(this._outpaintPromptInput);
+};
+
+@action
+processOutpaintingWithPrompt = async (customPrompt: string) => {
+    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._outpaintingOriginalWidth);
-        const origHeight = NumCast(this.Document._outpaintingOriginalHeight);
+    // Set flag that outpainting is in progress
+    this._outpaintingInProgress = true;
 
-        if (!origWidth || !origHeight) {
-            console.error('Original dimensions (_outpaintingOriginalWidth/_outpaintingOriginalHeight) not set. Ensure resizeViewForOutpainting was called first.');
+    try {
+        const currentPath = this.choosePath(field.url);
+        const newWidth = NumCast(this.Document._width);
+        const newHeight = NumCast(this.Document._height);
+
+        // Revert dimensions if prompt is blank (acts like Cancel)
+        if (!customPrompt) {
+            this.Document._width = origWidth;
+            this.Document._height = origHeight;
+            this._outpaintingInProgress = false;
             return;
         }
 
-        //alert(`Original dimensions: ${origWidth} x ${origHeight}`);
-
-        // Set flag that outpainting is in progress
-        this._outpaintingInProgress = true;
-
-        try {
-            // Get the current path to the image
-            const currentPath = this.choosePath(field.url);
-
-            // Get original and new dimensions for calculating mask
-            const newWidth = NumCast(this.Document._width);
-            const newHeight = NumCast(this.Document._height);
-
-            // Optional: Ask user for a prompt to guide the outpainting
-            let prompt = 'Extend this image naturally with matching content';
-            const customPrompt = await new Promise<string>(resolve => {
-                const dialog = document.createElement('div');
-                Object.assign(dialog.style, {
-                    position: 'fixed',
-                    top: '50%',
-                    left: '50%',
-                    transform: 'translate(-50%, -50%)',
-                    background: 'white',
-                    padding: '20px',
-                    borderRadius: '8px',
-                    boxShadow: '0 4px 12px rgba(0,0,0,0.2)',
-                    zIndex: '10000',
-                });
-
-                const title = document.createElement('h3');
-                title.style.marginTop = '0';
-                title.textContent = 'Outpaint Image';
-
-                const description = document.createElement('p');
-                description.textContent = 'Enter a prompt for extending the image:';
-
-                const input = document.createElement('input');
-                input.id = 'outpaint-prompt';
-                input.type = 'text';
-                input.value = 'Extend this image naturally with matching content';
-                Object.assign(input.style, {
-                    width: '300px',
-                    padding: '8px',
-                    marginBottom: '10px',
-                });
-
-                const buttonContainer = document.createElement('div');
-                Object.assign(buttonContainer.style, {
-                    display: 'flex',
-                    justifyContent: 'flex-end',
-                    gap: '10px',
-                });
-
-                const cancelButton = document.createElement('button');
-                cancelButton.textContent = 'Cancel';
-
-                const confirmButton = document.createElement('button');
-                confirmButton.textContent = 'Generate';
-                Object.assign(confirmButton.style, {
-                    background: '#0078d4',
-                    color: 'white',
-                    border: 'none',
-                    padding: '8px 16px',
-                });
-
-                buttonContainer.appendChild(cancelButton);
-                buttonContainer.appendChild(confirmButton);
-
-                dialog.appendChild(title);
-                dialog.appendChild(description);
-                dialog.appendChild(input);
-                dialog.appendChild(buttonContainer);
-
-                document.body.appendChild(dialog);
-
-                cancelButton.onclick = () => {
-                    document.body.removeChild(dialog);
-                    resolve('');
-                };
+        // Optional: add loading indicator
+        const loadingOverlay = document.createElement('div');
+        loadingOverlay.style.position = 'absolute';
+        loadingOverlay.style.top = '0';
+        loadingOverlay.style.left = '0';
+        loadingOverlay.style.width = '100%';
+        loadingOverlay.style.height = '100%';
+        loadingOverlay.style.background = 'rgba(0,0,0,0.5)';
+        loadingOverlay.style.display = 'flex';
+        loadingOverlay.style.justifyContent = 'center';
+        loadingOverlay.style.alignItems = 'center';
+        loadingOverlay.innerHTML = '<div style="color: white; font-size: 16px;">Generating outpainted image...</div>';
+        this._mainCont?.appendChild(loadingOverlay);
+
+        const response = await Networking.PostToServer('/outpaintImage', {
+            imageUrl: currentPath,
+            prompt: customPrompt,
+            originalDimensions: { width: origWidth, height: origHeight },
+            newDimensions: { width: newWidth, height: newHeight },
+        });
 
-                confirmButton.onclick = () => {
-                    const promptValue = input.value;
-                    document.body.removeChild(dialog);
-                    resolve(promptValue);
-                };
-            });
+        if (response && typeof response === 'object' && 'url' in response && typeof response.url === 'string') {
+            console.log('Received outpainted image:', response.url);
 
-            // If user cancelled, reset dimensions to original
-            if (!customPrompt) {
-                this.Document._width = origWidth;
-                this.Document._height = origHeight;
-                this._outpaintingInProgress = false;
-                return;
+            if (!this.dataDoc[this.fieldKey + '_alternates']) {
+                this.dataDoc[this.fieldKey + '_alternates'] = new List<Doc>();
             }
 
-            // Show loading indicator
-            const loadingOverlay = document.createElement('div');
-            loadingOverlay.style.position = 'absolute';
-            loadingOverlay.style.top = '0';
-            loadingOverlay.style.left = '0';
-            loadingOverlay.style.width = '100%';
-            loadingOverlay.style.height = '100%';
-            loadingOverlay.style.background = 'rgba(0,0,0,0.5)';
-            loadingOverlay.style.display = 'flex';
-            loadingOverlay.style.justifyContent = 'center';
-            loadingOverlay.style.alignItems = 'center';
-            loadingOverlay.innerHTML = '<div style="color: white; font-size: 16px;">Generating outpainted image...</div>';
-            this._mainCont?.appendChild(loadingOverlay);
-
-            // Call the outpaint API
-            const response = await Networking.PostToServer('/outpaintImage', {
-                imageUrl: currentPath,
-                prompt: customPrompt,
-                originalDimensions: {
-                    width: origWidth,
-                    height: origHeight,
-                },
-                newDimensions: {
-                    width: newWidth,
-                    height: newHeight,
-                },
+            const originalDoc = Docs.Create.ImageDocument(field.url.href, {
+                title: `Original: ${this.Document.title}`,
+                _nativeWidth: Doc.NativeWidth(this.dataDoc),
+                _nativeHeight: Doc.NativeHeight(this.dataDoc),
             });
-            if (response && typeof response === 'object' && 'url' in response && typeof response.url === 'string') {
-                console.log('Response is valid and contains URL:', response.url);
-            } else {
-                console.error('Unexpected API response:', response);
-                alert('Failed to receive a valid image URL from server.');
-            }
-
-            if (response && 'url' in response && typeof response.url === 'string') {
-                // Save the original image as an alternate
-                if (!this.dataDoc[this.fieldKey + '_alternates']) {
-                    this.dataDoc[this.fieldKey + '_alternates'] = new List<Doc>();
-                }
-
-                // Create a copy of the current image as an alternate
-                const originalDoc = Docs.Create.ImageDocument(field.url.href, {
-                    title: `Original: ${this.Document.title}`,
-                    _nativeWidth: Doc.NativeWidth(this.dataDoc),
-                    _nativeHeight: Doc.NativeHeight(this.dataDoc),
-                });
-
-                // Add to alternates
-                Doc.AddDocToList(this.dataDoc, this.fieldKey + '_alternates', originalDoc);
 
-                // Update the image with the outpainted version
-                this.dataDoc[this.fieldKey] = new ImageField(response.url);
+            Doc.AddDocToList(this.dataDoc, this.fieldKey + '_alternates', originalDoc);
 
-                // Update native dimensions
-                Doc.SetNativeWidth(this.dataDoc, newWidth);
-                Doc.SetNativeHeight(this.dataDoc, newHeight);
+            // Replace with new outpainted image
+            this.dataDoc[this.fieldKey] = new ImageField(response.url);
 
-                // Add AI metadata
-                this.Document.ai = true;
-                this.Document.ai_outpainted = true;
-                this.Document.ai_outpaint_prompt = customPrompt;
-            } else {
-                // If failed, revert to original dimensions
-                this.Document._width = origWidth;
-                this.Document._height = origHeight;
-                alert('Failed to outpaint image. Please try again.');
-            }
+            Doc.SetNativeWidth(this.dataDoc, newWidth);
+            Doc.SetNativeHeight(this.dataDoc, newHeight);
 
-            // Remove loading overlay
-            this._mainCont?.removeChild(loadingOverlay);
-        } catch (error) {
-            console.error('Error during outpainting:', error);
-            // Revert to original dimensions on error
+            this.Document.ai = true;
+            this.Document.ai_outpainted = true;
+            this.Document.ai_outpaint_prompt = customPrompt;
+        } else {
+            console.error('Unexpected API response:', response);
             this.Document._width = origWidth;
             this.Document._height = origHeight;
-            alert('An error occurred while outpainting. Please try again.');
-        } finally {
-            // Clear the outpainting flags
-            this._outpaintingInProgress = false;
-            delete this.Document._originalDims;
+            alert('Failed to receive a valid image URL from server.');
         }
-    };
+
+        this._mainCont?.removeChild(loadingOverlay);
+    } catch (error) {
+        console.error('Error during outpainting:', error);
+        this.Document._width = origWidth;
+        this.Document._height = origHeight;
+        alert('An error occurred while outpainting. Please try again.');
+    } finally {
+        this._outpaintingInProgress = false;
+        delete this.Document._originalDims;
+    }
+};
+
+processOutpainting = () => {
+  this.openOutpaintPrompt();
+};
+
+componentUI = () => (
+  <>
+    {this._showOutpaintPrompt && (
+      <div className="imageBox-regenerate-dialog">
+        <h3>Outpaint Image</h3>
+        <p>Enter a prompt for extending the image:</p>
+        <input
+          type="text"
+          value={this._outpaintPromptInput}
+          onChange={this.handlePromptChange}
+        />
+        <div className="buttons">
+          <button onClick={this.closeOutpaintPrompt}>Cancel</button>
+          <button className="generate-btn" onClick={this.submitOutpaintPrompt}>
+            Generate
+          </button>
+        </div>
+      </div>
+    )}
+  </>
+);    
+
+    // // Add this method to process outpainting when resize is complete
+    // @action
+    // processOutpainting = async () => {
+    //     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;
+    //     }
+
+    //     //alert(`Original dimensions: ${origWidth} x ${origHeight}`);
+
+    //     // Set flag that outpainting is in progress
+    //     this._outpaintingInProgress = true;
+
+    //     try {
+    //         // Get the current path to the image
+    //         const currentPath = this.choosePath(field.url);
+
+    //         // Get original and new dimensions for calculating mask
+    //         const newWidth = NumCast(this.Document._width);
+    //         const newHeight = NumCast(this.Document._height);
+
+    //         // Optional: Ask user for a prompt to guide the outpainting
+    //         let prompt = 'Extend this image naturally with matching content';
+    //         const customPrompt = await new Promise<string>(resolve => {
+    //             const dialog = document.createElement('div');
+    //             Object.assign(dialog.style, {
+    //                 position: 'fixed',
+    //                 top: '50%',
+    //                 left: '50%',
+    //                 transform: 'translate(-50%, -50%)',
+    //                 background: 'white',
+    //                 padding: '20px',
+    //                 borderRadius: '8px',
+    //                 boxShadow: '0 4px 12px rgba(0,0,0,0.2)',
+    //                 zIndex: '10000',
+    //             });
+
+    //             const title = document.createElement('h3');
+    //             title.style.marginTop = '0';
+    //             title.textContent = 'Outpaint Image';
+
+    //             const description = document.createElement('p');
+    //             description.textContent = 'Enter a prompt for extending the image:';
+
+    //             const input = document.createElement('input');
+    //             input.id = 'outpaint-prompt';
+    //             input.type = 'text';
+    //             input.value = 'Extend this image naturally with matching content';
+    //             Object.assign(input.style, {
+    //                 width: '300px',
+    //                 padding: '8px',
+    //                 marginBottom: '10px',
+    //             });
+
+    //             const buttonContainer = document.createElement('div');
+    //             Object.assign(buttonContainer.style, {
+    //                 display: 'flex',
+    //                 justifyContent: 'flex-end',
+    //                 gap: '10px',
+    //             });
+
+    //             const cancelButton = document.createElement('button');
+    //             cancelButton.textContent = 'Cancel';
+
+    //             const confirmButton = document.createElement('button');
+    //             confirmButton.textContent = 'Generate';
+    //             Object.assign(confirmButton.style, {
+    //                 background: '#0078d4',
+    //                 color: 'white',
+    //                 border: 'none',
+    //                 padding: '8px 16px',
+    //             });
+
+    //             buttonContainer.appendChild(cancelButton);
+    //             buttonContainer.appendChild(confirmButton);
+
+    //             dialog.appendChild(title);
+    //             dialog.appendChild(description);
+    //             dialog.appendChild(input);
+    //             dialog.appendChild(buttonContainer);
+
+    //             document.body.appendChild(dialog);
+
+    //             cancelButton.onclick = () => {
+    //                 document.body.removeChild(dialog);
+    //                 resolve('');
+    //             };
+
+    //             confirmButton.onclick = () => {
+    //                 const promptValue = input.value;
+    //                 document.body.removeChild(dialog);
+    //                 resolve(promptValue);
+    //             };
+    //         });
+
+    //         // If user cancelled, reset dimensions to original
+    //         if (!customPrompt) {
+    //             this.Document._width = origWidth;
+    //             this.Document._height = origHeight;
+    //             this._outpaintingInProgress = false;
+    //             return;
+    //         }
+
+    //         // Show loading indicator
+    //         const loadingOverlay = document.createElement('div');
+    //         loadingOverlay.style.position = 'absolute';
+    //         loadingOverlay.style.top = '0';
+    //         loadingOverlay.style.left = '0';
+    //         loadingOverlay.style.width = '100%';
+    //         loadingOverlay.style.height = '100%';
+    //         loadingOverlay.style.background = 'rgba(0,0,0,0.5)';
+    //         loadingOverlay.style.display = 'flex';
+    //         loadingOverlay.style.justifyContent = 'center';
+    //         loadingOverlay.style.alignItems = 'center';
+    //         loadingOverlay.innerHTML = '<div style="color: white; font-size: 16px;">Generating outpainted image...</div>';
+    //         this._mainCont?.appendChild(loadingOverlay);
+
+    //         // Call the outpaint API
+    //         const response = await Networking.PostToServer('/outpaintImage', {
+    //             imageUrl: currentPath,
+    //             prompt: customPrompt,
+    //             originalDimensions: {
+    //                 width: origWidth,
+    //                 height: origHeight,
+    //             },
+    //             newDimensions: {
+    //                 width: newWidth,
+    //                 height: newHeight,
+    //             },
+    //         });
+    //         if (response && typeof response === 'object' && 'url' in response && typeof response.url === 'string') {
+    //             console.log('Response is valid and contains URL:', response.url);
+    //         } else {
+    //             console.error('Unexpected API response:', response);
+    //             alert('Failed to receive a valid image URL from server.');
+    //         }
+
+    //         if (response && 'url' in response && typeof response.url === 'string') {
+    //             // Save the original image as an alternate
+    //             if (!this.dataDoc[this.fieldKey + '_alternates']) {
+    //                 this.dataDoc[this.fieldKey + '_alternates'] = new List<Doc>();
+    //             }
+
+    //             // Create a copy of the current image as an alternate
+    //             const originalDoc = Docs.Create.ImageDocument(field.url.href, {
+    //                 title: `Original: ${this.Document.title}`,
+    //                 _nativeWidth: Doc.NativeWidth(this.dataDoc),
+    //                 _nativeHeight: Doc.NativeHeight(this.dataDoc),
+    //             });
+
+    //             // Add to alternates
+    //             Doc.AddDocToList(this.dataDoc, this.fieldKey + '_alternates', originalDoc);
+
+    //             // Update the image with the outpainted version
+    //             this.dataDoc[this.fieldKey] = new ImageField(response.url);
+
+    //             // Update native dimensions
+    //             Doc.SetNativeWidth(this.dataDoc, newWidth);
+    //             Doc.SetNativeHeight(this.dataDoc, newHeight);
+
+    //             // Add AI metadata
+    //             this.Document.ai = true;
+    //             this.Document.ai_outpainted = true;
+    //             this.Document.ai_outpaint_prompt = customPrompt;
+    //         } else {
+    //             // If failed, revert to original dimensions
+    //             this.Document._width = origWidth;
+    //             this.Document._height = origHeight;
+    //             alert('Failed to outpaint image. Please try again.');
+    //         }
+
+    //         // Remove loading overlay
+    //         this._mainCont?.removeChild(loadingOverlay);
+    //     } catch (error) {
+    //         console.error('Error during outpainting:', error);
+    //         // Revert to original dimensions on error
+    //         this.Document._width = origWidth;
+    //         this.Document._height = origHeight;
+    //         alert('An error occurred while outpainting. Please try again.');
+    //     } finally {
+    //         // Clear the outpainting flags
+    //         this._outpaintingInProgress = false;
+    //         delete this.Document._originalDims;
+    //     }
+    // };
 
     specificContextMenu = (): void => {
         const field = Cast(this.dataDoc[this.fieldKey], ImageField);
@@ -586,10 +729,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             funcs.push({
                 description: 'Outpaint Image',
                 event: () => {
-                    this.processOutpainting();
+                    this.openOutpaintPrompt();
                 },
                 icon: 'brush',
             });
+            
             // Add outpainting history option if the image was outpainted
             this.Document.ai_outpainted &&
                 funcs.push({
-- 
cgit v1.2.3-70-g09d2


From 06bc78476ef050b8ce12a015a7d5492e01aa8cb8 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 21 Apr 2025 14:08:53 -0400
Subject: added undo for outpaint

---
 src/client/views/nodes/ImageBox.tsx | 24 +++++++++++++-----------
 1 file changed, 13 insertions(+), 11 deletions(-)

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

diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index c3df82611..7ad3ee2b6 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -27,7 +27,7 @@ import { Networking } from '../../Network';
 import { DragManager } from '../../util/DragManager';
 import { SettingsManager } from '../../util/SettingsManager';
 import { SnappingManager } from '../../util/SnappingManager';
-import { undoable, undoBatch } from '../../util/UndoManager';
+import { undoable, undoBatch, UndoManager } from '../../util/UndoManager';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
@@ -367,7 +367,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     @action
     handlePromptChange = (val: string | number) => {
-        this._outpaintPromptInput = val;
+        this._outpaintPromptInput = '' + val;
     };
 
     @action
@@ -392,19 +392,19 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         // Set flag that outpainting is in progress
         this._outpaintingInProgress = true;
 
+        // Revert dimensions if prompt is blank (acts like Cancel)
+        if (!customPrompt) {
+            this.Document._width = origWidth;
+            this.Document._height = origHeight;
+            this._outpaintingInProgress = false;
+            return;
+        }
+
         try {
             const currentPath = this.choosePath(field.url);
             const newWidth = NumCast(this.Document._width);
             const newHeight = NumCast(this.Document._height);
 
-            // Revert dimensions if prompt is blank (acts like Cancel)
-            if (!customPrompt) {
-                this.Document._width = origWidth;
-                this.Document._height = origHeight;
-                this._outpaintingInProgress = false;
-                return;
-            }
-
             // Optional: add loading indicator
             const loadingOverlay = document.createElement('div');
             loadingOverlay.style.position = 'absolute';
@@ -426,6 +426,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 newDimensions: { width: newWidth, height: newHeight },
             });
 
+            const batch = UndoManager.StartBatch('outpaint image');
             if (response && typeof response === 'object' && 'url' in response && typeof response.url === 'string') {
                 console.log('Received outpainted image:', response.url);
 
@@ -456,6 +457,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 this.Document._height = origHeight;
                 alert('Failed to receive a valid image URL from server.');
             }
+            batch.end();
 
             this._mainCont?.removeChild(loadingOverlay);
         } catch (error) {
@@ -464,7 +466,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             this.Document._height = origHeight;
             alert('An error occurred while outpainting. Please try again.');
         } finally {
-            this._outpaintingInProgress = false;
+            runInAction(() => (this._outpaintingInProgress = false));
         }
     };
 
-- 
cgit v1.2.3-70-g09d2


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

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

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

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


From 6be2ce925e3a0ba1e88e568ddbc57e3a272e6ff0 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 23 Apr 2025 15:02:50 -0400
Subject: fixing bad merge

---
 src/client/views/nodes/ImageBox.tsx | 31 ++++++++++++++++++++++++++++++-
 1 file changed, 30 insertions(+), 1 deletion(-)

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

diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index c59c38fd8..1fcabaf21 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -8,7 +8,7 @@ import { extname } from 'path';
 import * as React from 'react';
 import { AiOutlineSend } from 'react-icons/ai';
 import ReactLoading from 'react-loading';
-import { ClientUtils, DashColor, returnFalse, returnOne, returnZero, setupMoveUpEvents, UpdateIcon } from '../../../ClientUtils';
+import { ClientUtils, DashColor, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, UpdateIcon } from '../../../ClientUtils';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
@@ -948,6 +948,35 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                         addDocument={this.addDocument}>
                         {this.content}
                     </CollectionFreeFormView>
+                    {this.Loading ? (
+                        <div className="loading-spinner" style={{ position: 'absolute' }}>
+                            <ReactLoading type="spin" height={50} width={50} color={'blue'} />
+                        </div>
+                    ) : null}
+                    {this.regenerateImageIcon}
+                    {this.overlayImageIcon}
+                    {this.annotationLayer}
+                    {!this._mainCont || !this.DocumentView || !this._annotationLayer.current ? null : (
+                        <MarqueeAnnotator
+                            Document={this.Document}
+                            ref={this.marqueeref}
+                            scrollTop={0}
+                            annotationLayerScrollTop={0}
+                            scaling={returnOne}
+                            annotationLayerScaling={this._props.NativeDimScaling}
+                            screenTransform={this.DocumentView().screenToViewTransform}
+                            docView={this.DocumentView}
+                            addDocument={this.addDocument}
+                            finishMarquee={this.finishMarquee}
+                            savedAnnotations={this.savedAnnotations}
+                            selectionText={returnEmptyString}
+                            annotationLayer={this._annotationLayer.current}
+                            marqueeContainer={this._mainCont}
+                            highlightDragSrcColor=""
+                            anchorMenuCrop={this.crop}
+                            // anchorMenuFlashcard={() => this.getImageDesc()}
+                        />
+                    )}
                     {this._outpaintingInProgress && (
                         <div className="imageBox-outpaintingSpinner">
                             <ReactLoading type="spin" color="#666" height={60} width={60} />
-- 
cgit v1.2.3-70-g09d2


From 0d758430301d934a465ea090abf41f4a596a146c Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 23 Apr 2025 15:29:26 -0400
Subject: fixed outpainting to get authorization for dropbox

---
 src/client/documents/Documents.ts                 | 63 ++++++-----------------
 src/client/views/nodes/ImageBox.tsx               | 55 ++++++++++----------
 src/client/views/smartdraw/DrawingFillHandler.tsx |  6 ++-
 3 files changed, 49 insertions(+), 75 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 2a2f9d342..e694419a4 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -526,18 +526,17 @@ export class DocumentOptions {
     ai_firefly_seed?: number;
     ai_firefly_prompt?: string;
 
-      /**
-   * JSON‐stringified slot configuration for ScrapbookBox
-   */
-  scrapbookConfig?: string;
-
-  /**
-   * 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);
+    /**
+     * JSON‐stringified slot configuration for ScrapbookBox
+     */
+    scrapbookConfig?: string;
 
+    /**
+     * 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();
@@ -587,31 +586,6 @@ export namespace Docs {
                     options: { acl: '' },
                 },
             ],
-
-            // AARAV ADD //
-            [
-                DocumentType.JOURNAL,
-                {
-                    layout: { view: EmptyBox, dataField: 'text' },
-                    options: {
-                        title: 'Daily Journal',
-                        acl_Guest: SharingPermissions.View,
-                    },
-                },
-            ],
-            // AARAV ADD //
-
-            [
-                DocumentType.SCRAPBOOK,
-                {
-                    layout: { view: EmptyBox, dataField: 'text' },
-                    options: {
-                        title: 'Scrapbook',
-                        acl_Guest: SharingPermissions.View,
-                    },
-                    
-                },
-            ],
         ]);
 
         const suffix = 'Proto';
@@ -944,18 +918,17 @@ export namespace Docs {
             return InstanceFromProto(Prototypes.get(DocumentType.RTF), field, options, undefined, fieldKey);
         }
 
-        export function ScrapbookDocument(
-            items: Doc[] = [],
-            options: DocumentOptions = {},
-            fieldKey: string = 'items'
-        ) {
+        export function ScrapbookDocument(items: Doc[] = [], options: DocumentOptions = {}, fieldKey: string = 'items') {
             return InstanceFromProto(
                 Prototypes.get(DocumentType.SCRAPBOOK),
                 new List<Doc>(items),
                 {
-                    title: options.title
-                        ?? new Date().toLocaleDateString(undefined, {
-                            year: 'numeric', month: 'short', day: 'numeric'
+                    title:
+                        options.title ??
+                        new Date().toLocaleDateString(undefined, {
+                            year: 'numeric',
+                            month: 'short',
+                            day: 'numeric',
                         }),
                     ...options,
                 },
@@ -1006,8 +979,6 @@ export namespace Docs {
 
         // AARAV ADD //
 
-     
-
         export function LinkDocument(source: Doc, target: Doc, options: DocumentOptions = {}, id?: string) {
             const linkDoc = InstanceFromProto(
                 Prototypes.get(DocumentType.LINK),
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 1fcabaf21..9c6b8e99c 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -140,7 +140,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     };
 
     componentDidMount() {
-        super.componentDidMount?.();
         this._disposers.sizer = reaction(
             () => ({
                 forceFull: this._props.renderDepth < 1 || this.layoutDoc._showFullRes,
@@ -426,38 +425,40 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 newDimensions: { width: newWidth, height: newHeight },
             });
 
-            const batch = UndoManager.StartBatch('outpaint image');
-            if (response && typeof response === 'object' && 'url' in response && typeof response.url === 'string') {
-                console.log('Received outpainted image:', response.url);
-
-                if (!this.dataDoc[this.fieldKey + '_alternates']) {
-                    this.dataDoc[this.fieldKey + '_alternates'] = new List<Doc>();
-                }
+            const error = ('error' in response && (response.error as string)) || '';
+            if (error.includes('Dropbox') && confirm('Outpaint image failed. Try authorizing DropBox?\r\n' + error.replace(/^[^"]*/, ''))) {
+                DrawingFillHandler.authorizeDropbox();
+            } else {
+                const batch = UndoManager.StartBatch('outpaint image');
+                if (response && typeof response === 'object' && 'url' in response && typeof response.url === 'string') {
+                    if (!this.dataDoc[this.fieldKey + '_alternates']) {
+                        this.dataDoc[this.fieldKey + '_alternates'] = new List<Doc>();
+                    }
 
-                const originalDoc = Docs.Create.ImageDocument(field.url.href, {
-                    title: `Original: ${this.Document.title}`,
-                    _nativeWidth: Doc.NativeWidth(this.dataDoc),
-                    _nativeHeight: Doc.NativeHeight(this.dataDoc),
-                });
+                    const originalDoc = Docs.Create.ImageDocument(field.url.href, {
+                        title: `Original: ${this.Document.title}`,
+                        _nativeWidth: Doc.NativeWidth(this.dataDoc),
+                        _nativeHeight: Doc.NativeHeight(this.dataDoc),
+                    });
 
-                Doc.AddDocToList(this.dataDoc, this.fieldKey + '_alternates', originalDoc);
+                    Doc.AddDocToList(this.dataDoc, this.fieldKey + '_alternates', originalDoc);
 
-                // Replace with new outpainted image
-                this.dataDoc[this.fieldKey] = new ImageField(response.url);
+                    // Replace with new outpainted image
+                    this.dataDoc[this.fieldKey] = new ImageField(response.url);
 
-                Doc.SetNativeWidth(this.dataDoc, newWidth);
-                Doc.SetNativeHeight(this.dataDoc, newHeight);
+                    Doc.SetNativeWidth(this.dataDoc, newWidth);
+                    Doc.SetNativeHeight(this.dataDoc, newHeight);
 
-                this.Document.$ai = true;
-                this.Document.$ai_outpainted = true;
-                this.Document.$ai_outpaint_prompt = customPrompt;
-            } else {
-                console.error('Unexpected API response:', response);
-                this.Document._width = origWidth;
-                this.Document._height = origHeight;
-                alert('Failed to receive a valid image URL from server.');
+                    this.Document.$ai = true;
+                    this.Document.$ai_outpainted = true;
+                    this.Document.$ai_outpaint_prompt = customPrompt;
+                } else {
+                    this.Document._width = origWidth;
+                    this.Document._height = origHeight;
+                    alert('Failed to receive a valid image URL from server.');
+                }
+                batch.end();
             }
-            batch.end();
 
             this._mainCont?.removeChild(loadingOverlay);
         } catch (error) {
diff --git a/src/client/views/smartdraw/DrawingFillHandler.tsx b/src/client/views/smartdraw/DrawingFillHandler.tsx
index b0945fd83..2c69284db 100644
--- a/src/client/views/smartdraw/DrawingFillHandler.tsx
+++ b/src/client/views/smartdraw/DrawingFillHandler.tsx
@@ -12,6 +12,9 @@ import { AspectRatioLimits, FireflyDimensionsMap, FireflyImageDimensions, Firefl
 
 const DashDropboxId = '2m86iveqdr9vzsa';
 export class DrawingFillHandler {
+    static authorizeDropbox = () => {
+        window.open(`https://www.dropbox.com/oauth2/authorize?client_id=${DashDropboxId}&response_type=code&token_access_type=offline&redirect_uri=http://localhost:1050/refreshDropbox`, '_blank')?.focus();
+    };
     static drawingToImage = async (drawing: Doc, strength: number, user_prompt: string, styleDoc?: Doc) => {
         const tags = StrListCast(drawing.$tags).map(tag => tag.slice(1));
         const styles = tags.filter(tag => FireflyStylePresets.has(tag));
@@ -46,8 +49,7 @@ export class DrawingFillHandler {
                         .then(res => {
                             const error = ('error' in res && (res.error as string)) || '';
                             if (error.includes('Dropbox') && confirm('Create image failed. Try authorizing DropBox?\r\n' + error.replace(/^[^"]*/, ''))) {
-                                window.open(`https://www.dropbox.com/oauth2/authorize?client_id=${DashDropboxId}&response_type=code&token_access_type=offline&redirect_uri=http://localhost:1050/refreshDropbox`, '_blank')?.focus();
-                                return;
+                                return DrawingFillHandler.authorizeDropbox();
                             }
                             const genratedDocs = DocCast(drawing.ai_firefly_generatedDocs) ?? Docs.Create.MasonryDocument([], { title: StrCast(drawing.title) + ' AI Images', _width: 400, _height: 400 });
                             drawing.$ai_firefly_generatedDocs = genratedDocs;
-- 
cgit v1.2.3-70-g09d2


From 78ac87b8acf63079071e5e8805692ed8c30042ce Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 23 Apr 2025 22:02:36 -0400
Subject: lots of typechecking fixes.

---
 .../apis/google_docs/GooglePhotosClientUtils.ts    |  2 +-
 src/client/util/CalendarManager.tsx                |  2 +-
 src/client/util/DocumentManager.ts                 |  8 +++---
 src/client/util/LinkManager.ts                     |  3 +-
 src/client/util/ReplayMovements.ts                 |  6 ++--
 src/client/util/ScriptManager.ts                   |  9 ++----
 src/client/util/SearchUtil.ts                      |  5 ++--
 .../util/reportManager/ReportManagerComponents.tsx |  4 +--
 src/client/views/DashboardView.tsx                 | 28 +++++++++----------
 src/client/views/InkStrokeProperties.ts            |  8 +++---
 src/client/views/InkingStroke.tsx                  | 12 ++++----
 src/client/views/PropertiesButtons.tsx             | 23 ++++++++--------
 .../views/PropertiesDocBacklinksSelector.tsx       |  8 ++----
 src/client/views/PropertiesDocContextSelector.tsx  |  4 +--
 src/client/views/UndoStack.tsx                     |  5 +---
 src/client/views/animationtimeline/Region.tsx      | 32 ++++++++++------------
 src/client/views/animationtimeline/Track.tsx       | 24 ++++++++--------
 src/client/views/collections/CollectionMenu.tsx    |  4 +--
 .../collections/CollectionNoteTakingViewColumn.tsx |  2 +-
 .../views/collections/CollectionPileView.tsx       |  1 -
 .../collections/CollectionStackedTimeline.tsx      |  2 +-
 .../views/collections/CollectionTreeView.tsx       |  8 +++---
 .../views/collections/FlashcardPracticeUI.tsx      |  2 +-
 src/client/views/collections/TreeView.tsx          |  4 +--
 .../CollectionFreeFormLayoutEngines.tsx            |  2 +-
 .../collectionFreeForm/FaceCollectionBox.tsx       | 13 +++++----
 .../collectionSchema/SchemaColumnHeader.tsx        |  2 +-
 .../collectionSchema/SchemaTableCell.tsx           | 18 ++++++------
 src/client/views/linking/LinkMenuGroup.tsx         |  5 ++--
 src/client/views/linking/LinkMenuItem.tsx          |  4 +--
 .../views/newlightbox/ExploreView/ExploreView.tsx  |  4 +--
 src/client/views/newlightbox/NewLightboxView.tsx   |  7 ++---
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   | 16 +++++------
 .../views/nodes/DataVizBox/SchemaCSVPopUp.tsx      |  6 ++--
 .../nodes/DataVizBox/components/Histogram.tsx      | 17 ++++++------
 .../nodes/DataVizBox/components/LineChart.tsx      | 25 +++++++++++------
 .../views/nodes/DataVizBox/components/TableBox.tsx |  2 +-
 src/client/views/nodes/EquationBox.tsx             |  1 -
 src/client/views/nodes/FieldView.tsx               |  7 +++--
 src/client/views/nodes/FunctionPlotBox.tsx         | 11 ++++----
 src/client/views/nodes/ImageBox.tsx                |  2 +-
 .../views/nodes/MapBox/DirectionsAnchorMenu.tsx    |  2 +-
 src/client/views/nodes/calendarBox/CalendarBox.tsx |  3 +-
 .../nodes/chatbot/chatboxcomponents/ChatBox.tsx    | 20 ++++++++------
 .../views/nodes/chatbot/tools/GetDocsTool.ts       |  5 +++-
 .../views/nodes/chatbot/vectorstore/Vectorstore.ts | 15 +++++-----
 .../views/nodes/formattedText/DailyJournal.tsx     |  2 +-
 src/client/views/nodes/scrapbook/ScrapbookBox.tsx  |  2 +-
 src/client/views/pdf/Annotation.tsx                |  7 +++--
 src/client/views/search/FaceRecognitionHandler.tsx |  6 ++--
 src/fields/Doc.ts                                  |  2 +-
 src/fields/Types.ts                                |  2 ++
 52 files changed, 207 insertions(+), 207 deletions(-)

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

diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
index 4b86a8341..15fd6313a 100644
--- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts
+++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
@@ -311,7 +311,7 @@ export namespace GooglePhotos {
             sources
                 .filter(source => ImageCast(Doc.GetProto(source).data))
                 .forEach(async source => {
-                    const data = ImageCast(Doc.GetProto(source).data);
+                    const data = ImageCast(Doc.GetProto(source).data)!;
                     const url = data.url.href;
                     const target = Doc.MakeEmbedding(source);
                     const description = parseDescription(target, descriptionKey);
diff --git a/src/client/util/CalendarManager.tsx b/src/client/util/CalendarManager.tsx
index 4e321a893..b50e39c02 100644
--- a/src/client/util/CalendarManager.tsx
+++ b/src/client/util/CalendarManager.tsx
@@ -162,7 +162,7 @@ export class CalendarManager extends ObservableReactComponent<object> {
 
             console.log('my calendars: ', Doc.MyCalendars);
             if (this.creationType === 'new-calendar') {
-                Doc.AddDocToList(Doc.MyCalendars, 'data', calendar); // add to new calendar to dashboard calendars
+                Doc.MyCalendars && Doc.AddDocToList(Doc.MyCalendars, 'data', calendar); // add to new calendar to dashboard calendars
             }
         }
     };
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index ad57c2a62..3bae2881e 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -173,10 +173,10 @@ export class DocumentManager {
         while (
             containerDocContext.length &&
             DocCast(containerDocContext[0]?.embedContainer) &&
-            DocCast(containerDocContext[0].embedContainer)?._type_collection !== CollectionViewType.Docking &&
+            DocCast(containerDocContext[0].embedContainer)!._type_collection !== CollectionViewType.Docking &&
             (includeExistingViews || !DocumentManager.Instance.getDocumentView(containerDocContext[0]))
         ) {
-            containerDocContext = [Cast(containerDocContext[0].embedContainer, Doc, null), ...containerDocContext];
+            containerDocContext = [DocCast(containerDocContext[0].embedContainer)!, ...containerDocContext];
         }
         return containerDocContext;
     }
@@ -248,7 +248,7 @@ export class DocumentManager {
         finished?: (changed: boolean) => void // func called after focusing on target with flag indicating whether anything needed to be done.
     ) => {
         const options = optionsIn;
-        Doc.RemoveDocFromList(Doc.MyRecentlyClosed, undefined, targetDoc);
+        Doc.MyRecentlyClosed && Doc.RemoveDocFromList(Doc.MyRecentlyClosed, undefined, targetDoc);
         const docContextPath = DocumentManager.GetContextPath(targetDoc, true);
         if (docContextPath.some(doc => doc.hidden)) options.toggleTarget = false;
         let activatedTab = false;
@@ -272,7 +272,7 @@ export class DocumentManager {
             }));
         if (options.openLocation?.includes(OpenWhere.lightbox)) {
             // even if we found the document view, if the target is a lightbox, we try to open it in the lightbox to preserve lightbox semantics (eg, there's only one active doc in the lightbox)
-            const target = DocCast(targetDoc.annotationOn, targetDoc);
+            const target = DocCast(targetDoc.annotationOn, targetDoc)!;
             const compView = this.getDocumentView(DocCast(target.embedContainer))?.ComponentView;
             if ((compView?.addDocTab ?? compView?._props.addDocTab)?.(target, options.openLocation)) {
                 await new Promise<void>(waitres => {
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 344e2e4c0..d8e0c4cbe 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -217,7 +217,8 @@ export class LinkManager {
     }
 
     // finds the opposite anchor of a given anchor in a link
-    public static getOppositeAnchor(linkDoc: Doc, anchor: Doc): Doc | undefined {
+    public static getOppositeAnchor(linkDoc: Doc | undefined, anchor: Doc | undefined): Doc | undefined {
+        if (!linkDoc || !anchor) return undefined;
         const id = LinkManager.anchorIndex(linkDoc, anchor);
         const a1 = DocCast(linkDoc.link_anchor_1);
         const a2 = DocCast(linkDoc.link_anchor_2);
diff --git a/src/client/util/ReplayMovements.ts b/src/client/util/ReplayMovements.ts
index 62a09a8bc..4f0423342 100644
--- a/src/client/util/ReplayMovements.ts
+++ b/src/client/util/ReplayMovements.ts
@@ -108,9 +108,11 @@ export class ReplayMovements {
 
         movements.forEach((movement, i) => {
             if (typeof movement.doc === 'string') {
-                movements[i].doc = IdToDoc(movement.doc);
-                if (!movements[i].doc) {
+                const doc = IdToDoc(movement.doc);
+                if (!doc) {
                     console.log('ERROR: tracked doc not found');
+                } else {
+                    movements[i].doc = doc;
                 }
             }
         });
diff --git a/src/client/util/ScriptManager.ts b/src/client/util/ScriptManager.ts
index 9158f6c0b..8c7f88bf6 100644
--- a/src/client/util/ScriptManager.ts
+++ b/src/client/util/ScriptManager.ts
@@ -1,7 +1,6 @@
-import { Doc, DocListCast } from '../../fields/Doc';
+import { Doc, DocListCast, StrListCast } from '../../fields/Doc';
 import { List } from '../../fields/List';
-import { listSpec } from '../../fields/Schema';
-import { Cast, StrCast } from '../../fields/Types';
+import { StrCast } from '../../fields/Types';
 import { Docs } from '../documents/Documents';
 import { ScriptingGlobals } from './ScriptingGlobals';
 
@@ -10,7 +9,6 @@ export class ScriptManager {
     // eslint-disable-next-line no-use-before-define
     private static _instance: ScriptManager;
     public static get Instance(): ScriptManager {
-        // eslint-disable-next-line no-return-assign
         return this._instance || (this._instance = new this());
     }
     private constructor() {
@@ -58,7 +56,7 @@ export class ScriptManager {
     public static addScriptToGlobals(scriptDoc: Doc): void {
         ScriptingGlobals.removeGlobal(StrCast(scriptDoc.name));
 
-        const params = Cast(scriptDoc['data-params'], listSpec('string'), []);
+        const params = StrListCast(scriptDoc['data-params']);
         const paramNames = params.reduce((o: string, p: string) => {
             let out = o;
             if (params.indexOf(p) === params.length - 1) {
@@ -69,7 +67,6 @@ export class ScriptManager {
             return out;
         }, '' as string);
 
-        // eslint-disable-next-line no-new-func
         const f = new Function(paramNames, StrCast(scriptDoc.script));
 
         Object.defineProperty(f, 'name', { value: StrCast(scriptDoc.name), writable: false });
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index e4adcaa7e..fc3bb99ab 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -13,7 +13,7 @@ export namespace SearchUtil {
         const blockedKeys = matchKeyNames
             ? []
             : Object.entries(DocOptions)
-                  .filter(([, info]: [string, FInfo]) => !info?.searchable())
+                  .filter(([, info]: [string, FieldType | FInfo | undefined]) => (info instanceof FInfo ? !info.searchable() : true))
                   .map(([key]) => key);
 
         const exact = queryIn.startsWith('=');
@@ -22,8 +22,7 @@ export namespace SearchUtil {
         const results = new ObservableMap<Doc, string[]>();
         if (collectionDoc) {
             const docs = DocListCast(collectionDoc[Doc.LayoutDataKey(collectionDoc)]);
-            // eslint-disable-next-line @typescript-eslint/ban-types
-            const docIDs: String[] = [];
+            const docIDs: string[] = [];
             SearchUtil.foreachRecursiveDoc(docs, (depth: number, doc: Doc) => {
                 const dtype = StrCast(doc.type) as DocumentType;
                 if (dtype && !blockedTypes.includes(dtype) && !docIDs.includes(doc[Id]) && depth >= 0) {
diff --git a/src/client/util/reportManager/ReportManagerComponents.tsx b/src/client/util/reportManager/ReportManagerComponents.tsx
index 92f877859..80653779e 100644
--- a/src/client/util/reportManager/ReportManagerComponents.tsx
+++ b/src/client/util/reportManager/ReportManagerComponents.tsx
@@ -1,5 +1,3 @@
-/* eslint-disable react/require-default-props */
-/* eslint-disable prefer-destructuring */
 /* eslint-disable no-use-before-define */
 import * as React from 'react';
 import ReactMarkdown from 'react-markdown';
@@ -299,7 +297,7 @@ export function IssueView({ issue }: IssueViewProps) {
                     </div>
                 </div>
             )}
-            <ReactMarkdown className="issue-content" remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]}>
+            <ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]}>
                 {issueBody}
             </ReactMarkdown>
         </div>
diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx
index f61f6db18..3ceb23ffd 100644
--- a/src/client/views/DashboardView.tsx
+++ b/src/client/views/DashboardView.tsx
@@ -113,12 +113,12 @@ export class DashboardView extends ObservableReactComponent<object> {
 
     getDashboards = (whichGroup: DashboardGroup) => {
         if (whichGroup === DashboardGroup.MyDashboards) {
-            return DocListCast(Doc.MyDashboards.data).filter(dashboard => dashboard.$author === ClientUtils.CurrentUserEmail());
+            return DocListCast(Doc.MyDashboards?.data).filter(dashboard => dashboard.$author === ClientUtils.CurrentUserEmail());
         }
-        return DocListCast(Doc.MySharedDocs.data_dashboards).filter(doc => doc.dockingConfig);
+        return DocListCast(Doc.MySharedDocs?.data_dashboards).filter(doc => doc.dockingConfig);
     };
 
-    isUnviewedSharedDashboard = (dashboard: Doc) => !DocListCast(Doc.MySharedDocs.viewed).includes(dashboard);
+    isUnviewedSharedDashboard = (dashboard: Doc) => !DocListCast(Doc.MySharedDocs?.viewed).includes(dashboard);
 
     @undoBatch
     createNewDashboard = (name: string, background?: string) => {
@@ -155,7 +155,7 @@ export class DashboardView extends ObservableReactComponent<object> {
     @action
     openNewDashboardModal = () => {
         this.openModal = true;
-        this.setNewDashboardName(`Dashboard ${DocListCast(Doc.MyDashboards.data).length + 1}`);
+        this.setNewDashboardName(`Dashboard ${DocListCast(Doc.MyDashboards?.data).length + 1}`);
     };
 
     _downX: number = 0;
@@ -191,7 +191,7 @@ export class DashboardView extends ObservableReactComponent<object> {
                         <Button
                             text={'Shared Dashboards (' + this.getDashboards(DashboardGroup.SharedDashboards).length + ')'}
                             active={this.selectedDashboardGroup === DashboardGroup.SharedDashboards}
-                            color={this.getDashboards(DashboardGroup.SharedDashboards).some(dash => !DocListCast(Doc.MySharedDocs.viewed).includes(dash)) ? 'green' : color}
+                            color={this.getDashboards(DashboardGroup.SharedDashboards).some(dash => !DocListCast(Doc.MySharedDocs?.viewed).includes(dash)) ? 'green' : color}
                             align="flex-start"
                             onClick={() => this.selectDashboardGroup(DashboardGroup.SharedDashboards)}
                             fillWidth
@@ -275,7 +275,7 @@ export class DashboardView extends ObservableReactComponent<object> {
     }
 
     public static openSharedDashboard = (dashboard: Doc) => {
-        Doc.AddDocToList(Doc.MySharedDocs, 'viewed', dashboard);
+        Doc.MySharedDocs && Doc.AddDocToList(Doc.MySharedDocs, 'viewed', dashboard);
         DashboardView.openDashboard(Doc.BestEmbedding(dashboard));
     };
 
@@ -283,8 +283,8 @@ export class DashboardView extends ObservableReactComponent<object> {
     /// this also sets the readonly state of the dashboard based on the current mode of dash (from its url)
     public static openDashboard = (doc: Doc | undefined, fromHistory = false) => {
         if (!doc) return false;
-        Doc.AddDocToList(Doc.MyDashboards, 'data', doc);
-        Doc.RemoveDocFromList(Doc.MyRecentlyClosed, 'data', doc);
+        Doc.MyDashboards && Doc.AddDocToList(Doc.MyDashboards, 'data', doc);
+        Doc.MyRecentlyClosed && Doc.RemoveDocFromList(Doc.MyRecentlyClosed, 'data', doc);
 
         // this has the side-effect of setting the main container since we're assigning the active/guest dashboard
         Doc.UserDoc() ? (Doc.ActiveDashboard = doc) : (Doc.GuestDashboard = doc);
@@ -316,11 +316,11 @@ export class DashboardView extends ObservableReactComponent<object> {
     };
 
     public static removeDashboard = (dashboard: Doc) => {
-        const dashboards = DocListCast(Doc.MyDashboards.data).filter(dash => dash !== dashboard);
+        const dashboards = DocListCast(Doc.MyDashboards?.data).filter(dash => dash !== dashboard);
         undoable(() => {
             if (dashboard === Doc.ActiveDashboard) DashboardView.openDashboard(dashboards.lastElement());
-            Doc.RemoveDocFromList(Doc.MyDashboards, 'data', dashboard);
-            Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', dashboard, undefined, true, true);
+            Doc.MyDashboards && Doc.RemoveDocFromList(Doc.MyDashboards, 'data', dashboard);
+            Doc.MyRecentlyClosed && Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', dashboard, undefined, true, true);
             if (!dashboards.length) Doc.ActivePage = 'home';
         }, 'remove dashboard')();
     };
@@ -409,7 +409,7 @@ export class DashboardView extends ObservableReactComponent<object> {
     };
 
     public static createNewDashboard = (id?: string, name?: string, background?: string) => {
-        const dashboardCount = DocListCast(Doc.MyDashboards.data).length + 1;
+        const dashboardCount = DocListCast(Doc.MyDashboards?.data).length + 1;
         const freeformOptions: DocumentOptions = {
             x: 0,
             y: 400,
@@ -425,8 +425,8 @@ export class DashboardView extends ObservableReactComponent<object> {
         const freeformDoc = Doc.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions);
         const dashboardDoc = DashboardView.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: title }, id, 'row');
 
-        Doc.AddDocToList(Doc.MyHeaderBar, 'data', freeformDoc, undefined, undefined, true);
-        Doc.AddDocToList(Doc.MyDashboards, 'data', dashboardDoc);
+        Doc.MyHeaderBar && Doc.AddDocToList(Doc.MyHeaderBar, 'data', freeformDoc, undefined, undefined, true);
+        Doc.MyDashboards && Doc.AddDocToList(Doc.MyDashboards, 'data', dashboardDoc);
         freeformDoc._embedContainer = dashboardDoc;
         dashboardDoc.$myPaneCount = 1;
         dashboardDoc.$myOverlayDocs = new List<Doc>();
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 425f5b6bb..41f38c008 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -99,7 +99,7 @@ export class InkStrokeProperties {
             controls.splice(i, 4, ...splicepts.map(p => ({ X: p.x, Y: p.y })));
 
             // Updating the indices of the control points whose handle tangency has been broken.
-            doc.brokenInkIndices = new List(Cast(doc.brokenInkIndices, listSpec('number'), []).map(control => (control > i ? control + 4 : control)));
+            doc.brokenInkIndices = new List(NumListCast(doc.brokenInkIndices).map(control => (control > i ? control + 4 : control)));
             runInAction(() => {
                 this._currentPoint = -1;
             });
@@ -252,7 +252,7 @@ export class InkStrokeProperties {
             this.applyFunction(inkView, (view: DocumentView, ink: InkData) => {
                 const order = controlIndex % 4;
                 const closed = InkingStroke.IsClosed(ink);
-                const brokenIndices = Cast(inkView.Document.brokenInkIndices, listSpec('number'), []);
+                const brokenIndices = NumListCast(inkView.Document.brokenInkIndices);
                 if (origInk && this._currentPoint > 0 && this._currentPoint < ink.length - 1 && brokenIndices.findIndex(value => value === controlIndex) === -1) {
                     const cptBefore = ink[controlIndex];
                     const cpt = { X: cptBefore.X + deltaX, Y: cptBefore.Y + deltaY };
@@ -405,9 +405,9 @@ export class InkStrokeProperties {
         this.applyFunction(inkView, (view: DocumentView, ink: InkData) => {
             const doc = view.Document;
             const brokenIndices = Cast(doc.brokenInkIndices, listSpec('number'), []);
-            const ind = brokenIndices.findIndex(value => value === controlIndex);
+            const ind = brokenIndices?.findIndex(value => value === controlIndex) ?? -1;
             if (ind !== -1) {
-                brokenIndices.splice(ind, 1);
+                brokenIndices!.splice(ind, 1);
                 const [controlPoint, handleA, handleB] = [ink[controlIndex], ink[handleIndexA], ink[handleIndexB]];
                 const oppositeHandleA = this.rotatePoint(handleA, controlPoint, Math.PI);
                 const angleDifference = InkStrokeProperties.angleChange(handleB, oppositeHandleA, controlPoint);
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 0a49ea28e..0136f6abe 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -26,8 +26,8 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { DashColor, returnFalse, setupMoveUpEvents } from '../../ClientUtils';
 import { Doc } from '../../fields/Doc';
-import { InkData, InkField } from '../../fields/InkField';
-import { BoolCast, Cast, NumCast, RTFCast, StrCast } from '../../fields/Types';
+import { InkData } from '../../fields/InkField';
+import { BoolCast, InkCast, NumCast, RTFCast, StrCast } from '../../fields/Types';
 import { TraceMobx } from '../../fields/util';
 import { Gestures } from '../../pen-gestures/GestureTypes';
 import { Docs } from '../documents/Documents';
@@ -35,6 +35,7 @@ import { DocumentType } from '../documents/DocumentTypes';
 import { InteractionUtils } from '../util/InteractionUtils';
 import { SnappingManager } from '../util/SnappingManager';
 import { UndoManager } from '../util/UndoManager';
+import { CollectionFreeFormView } from './collections/collectionFreeForm';
 import { ContextMenu } from './ContextMenu';
 import { ViewBoxAnnotatableComponent } from './DocComponent';
 import { Colors } from './global/globalEnums';
@@ -42,14 +43,13 @@ import { InkControlPtHandles, InkEndPtHandles } from './InkControlPtHandles';
 import './InkStroke.scss';
 import { InkStrokeProperties } from './InkStrokeProperties';
 import { InkTangentHandles } from './InkTangentHandles';
+import { InkTranscription } from './InkTranscription';
+import { DocumentView } from './nodes/DocumentView';
 import { FieldView, FieldViewProps } from './nodes/FieldView';
 import { FormattedTextBox, FormattedTextBoxProps } from './nodes/formattedText/FormattedTextBox';
 import { PinDocView, PinProps } from './PinFuncs';
 import { StyleProp } from './StyleProp';
 import { ViewBoxInterface } from './ViewBoxInterface';
-import { InkTranscription } from './InkTranscription';
-import { CollectionFreeFormView } from './collections/collectionFreeForm';
-import { DocumentView } from './nodes/DocumentView';
 
 // eslint-disable-next-line @typescript-eslint/no-require-imports
 const { INK_MASK_SIZE } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
@@ -245,7 +245,7 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
      * factor for converting between ink and screen space.
      */
     inkScaledData = () => {
-        const inkData = Cast(this.dataDoc[this.fieldKey], InkField, Cast(this.layoutDoc[this.fieldKey], InkField, null) ?? null)?.inkData ?? [];
+        const inkData = InkCast(this.dataDoc[this.fieldKey], InkCast(this.layoutDoc[this.fieldKey]) ?? null)?.inkData ?? [];
         const inkStrokeWidth = NumCast(this.layoutDoc.stroke_width, 1);
         const inkTop = Math.min(...inkData.map(p => p.Y)) - inkStrokeWidth / 2;
         const inkBottom = Math.max(...inkData.map(p => p.Y)) + inkStrokeWidth / 2;
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 66936c64c..28566ba1d 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -385,18 +385,19 @@ export class PropertiesButtons extends React.Component {
             const followLoc = this.selectedDoc._followLinkLocation;
             const linkedToLightboxView = () => Doc.Links(this.selectedDoc).some(link => Doc.getOppositeAnchor(link, this.selectedDoc)?._isLightbox);
 
-            let active = false;
-            // prettier-ignore
-            switch (value[0]) {
-                case 'linkInPlace': active = linkButton && followLoc === OpenWhere.lightbox && !linkedToLightboxView();  break;
-                case 'linkOnRight': active = linkButton && followLoc === OpenWhere.addRight; break;
-                case 'enterPortal': active = linkButton && this.selectedDoc._followLinkLocation === OpenWhere.lightbox && linkedToLightboxView(); break;
-                case 'toggleDetail':active = ScriptCast(this.selectedDoc.onClick)?.script.originalScript.includes('toggleDetail'); break;
-                case 'nothing':     active = !linkButton && this.selectedDoc.onClick === undefined;break;
-                default:
-            }
+            const active = () => {
+                // prettier-ignore
+                switch (value[0]) {
+                    case 'linkInPlace': return linkButton && followLoc === OpenWhere.lightbox && !linkedToLightboxView();  break;
+                    case 'linkOnRight': return linkButton && followLoc === OpenWhere.addRight; break;
+                    case 'enterPortal': return linkButton && this.selectedDoc._followLinkLocation === OpenWhere.lightbox && linkedToLightboxView(); break;
+                    case 'toggleDetail':return ScriptCast(this.selectedDoc.onClick)?.script.originalScript.includes('toggleDetail'); break;
+                    case 'nothing':    return !linkButton && this.selectedDoc.onClick === undefined;break;
+                    default: return false;
+                }
+            };
             return (
-                <div className="list-item" key={`${value}`} style={{ backgroundColor: active ? Colors.LIGHT_BLUE : undefined }} onClick={click}>
+                <div className="list-item" key={`${value}`} style={{ backgroundColor: active() ? Colors.LIGHT_BLUE : undefined }} onClick={click}>
                     {value[1]}
                 </div>
             );
diff --git a/src/client/views/PropertiesDocBacklinksSelector.tsx b/src/client/views/PropertiesDocBacklinksSelector.tsx
index e30d14eae..5a00e6a89 100644
--- a/src/client/views/PropertiesDocBacklinksSelector.tsx
+++ b/src/client/views/PropertiesDocBacklinksSelector.tsx
@@ -1,18 +1,16 @@
-/* eslint-disable react/require-default-props */
-/* eslint-disable react/no-unused-prop-types */
 import { action } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc } from '../../fields/Doc';
-import { Cast } from '../../fields/Types';
+import { DocCast } from '../../fields/Types';
 import { DocumentType } from '../documents/DocumentTypes';
 import { LinkManager } from '../util/LinkManager';
 import { SettingsManager } from '../util/SettingsManager';
 import './PropertiesDocBacklinksSelector.scss';
 import { CollectionDockingView } from './collections/CollectionDockingView';
 import { LinkMenu } from './linking/LinkMenu';
-import { OpenWhere } from './nodes/OpenWhere';
 import { DocumentView } from './nodes/DocumentView';
+import { OpenWhere } from './nodes/OpenWhere';
 
 type PropertiesDocBacklinksSelectorProps = {
     Document: Doc;
@@ -26,7 +24,7 @@ export class PropertiesDocBacklinksSelector extends React.Component<PropertiesDo
     getOnClick = action((link: Doc) => {
         const linkAnchor = this.props.Document;
         const other = Doc.getOppositeAnchor(link, linkAnchor);
-        const otherdoc = !other ? undefined : other.annotationOn && other.type !== DocumentType.RTF ? Cast(other.annotationOn, Doc, null) : other;
+        const otherdoc = !other ? undefined : other.annotationOn && other.type !== DocumentType.RTF ? DocCast(other.annotationOn) : other;
         LinkManager.Instance.currentLink = link;
         if (otherdoc) {
             otherdoc.hidden = false;
diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx
index ee6486a9c..1aa956e16 100644
--- a/src/client/views/PropertiesDocContextSelector.tsx
+++ b/src/client/views/PropertiesDocContextSelector.tsx
@@ -3,7 +3,7 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc } from '../../fields/Doc';
 import { Id } from '../../fields/FieldSymbols';
-import { Cast, StrCast } from '../../fields/Types';
+import { DocCast, StrCast } from '../../fields/Types';
 import { ObservableReactComponent } from './ObservableReactComponent';
 import './PropertiesDocContextSelector.scss';
 import { CollectionDockingView } from './collections/CollectionDockingView';
@@ -29,7 +29,7 @@ export class PropertiesDocContextSelector extends ObservableReactComponent<Prope
         const target = this._props.DocView.Document;
         const targetContext = this._props.DocView.containerViewPath?.().lastElement()?.Document;
         const embeddings = Doc.GetEmbeddings(target);
-        const containerProtos = embeddings.filter(embedding => embedding.embedContainer && embedding.embedContainer instanceof Doc).reduce((set, embedding) => set.add(Cast(embedding.embedContainer, Doc, null)), new Set<Doc>());
+        const containerProtos = embeddings.filter(embedding => DocCast(embedding.embedContainer)).reduce((set, embedding) => set.add(DocCast(embedding.embedContainer)!), new Set<Doc>());
         const containerSets = Array.from(containerProtos.keys()).map(container => (Doc.GetEmbeddings(container).length ? Doc.GetEmbeddings(container) : [container]));
         const containers = containerSets.reduce((p, set) => {
             set.map(s => p.add(s));
diff --git a/src/client/views/UndoStack.tsx b/src/client/views/UndoStack.tsx
index 32b97b31a..067020a62 100644
--- a/src/client/views/UndoStack.tsx
+++ b/src/client/views/UndoStack.tsx
@@ -7,9 +7,8 @@ import { SettingsManager } from '../util/SettingsManager';
 import { UndoManager } from '../util/UndoManager';
 import './UndoStack.scss';
 
-interface UndoStackProps {}
 @observer
-export class UndoStack extends React.Component<UndoStackProps> {
+export class UndoStack extends React.Component<object> {
     render() {
         const background = UndoManager.batchCounter.get() ? 'yellow' : SettingsManager.userVariantColor;
         const color = UndoManager.batchCounter.get() ? 'black' : SettingsManager.userColor;
@@ -34,7 +33,6 @@ export class UndoStack extends React.Component<UndoStackProps> {
                                     {Array.from(UndoManager.undoStackNames).map((name, i) => (
                                         <div
                                             className="undoStack-resultContainer"
-                                            // eslint-disable-next-line react/no-array-index-key
                                             key={i}
                                             onClick={() => {
                                                 const size = UndoManager.undoStackNames.length;
@@ -48,7 +46,6 @@ export class UndoStack extends React.Component<UndoStackProps> {
                                         .map((name, i) => (
                                             <div
                                                 className="undoStack-resultContainer"
-                                                // eslint-disable-next-line react/no-array-index-key
                                                 key={i}
                                                 onClick={() => {
                                                     for (let n = 0; n <= i; n++) UndoManager.Redo();
diff --git a/src/client/views/animationtimeline/Region.tsx b/src/client/views/animationtimeline/Region.tsx
index 99163f6c6..40fcb2bf6 100644
--- a/src/client/views/animationtimeline/Region.tsx
+++ b/src/client/views/animationtimeline/Region.tsx
@@ -12,6 +12,18 @@ import './Region.scss';
 import './Timeline.scss';
 import { TimelineMenu } from './TimelineMenu';
 
+export const RegionDataSchema = createSchema({
+    position: defaultSpec('number', 0),
+    duration: defaultSpec('number', 0),
+    keyframes: listSpec(Doc),
+    fadeIn: defaultSpec('number', 0),
+    fadeOut: defaultSpec('number', 0),
+    functions: listSpec(Doc),
+    hasData: defaultSpec('boolean', false),
+});
+export type RegionData = makeInterface<[typeof RegionDataSchema]>;
+export const RegionData = makeInterface(RegionDataSchema);
+
 /**
  * Useful static functions that you can use. Mostly for logic, but you can also add UI logic here also
  */
@@ -108,18 +120,6 @@ export namespace RegionHelpers {
     };
 }
 
-export const RegionDataSchema = createSchema({
-    position: defaultSpec('number', 0),
-    duration: defaultSpec('number', 0),
-    keyframes: listSpec(Doc),
-    fadeIn: defaultSpec('number', 0),
-    fadeOut: defaultSpec('number', 0),
-    functions: listSpec(Doc),
-    hasData: defaultSpec('boolean', false),
-});
-export type RegionData = makeInterface<[typeof RegionDataSchema]>;
-export const RegionData = makeInterface(RegionDataSchema);
-
 interface IProps {
     animatedDoc: Doc;
     RegionData: Doc;
@@ -184,7 +184,7 @@ export class Region extends ObservableReactComponent<IProps> {
         return RegionHelpers.convertPixelTime(this.regiondata.fadeOut, 'mili', 'pixel', this._props.tickSpacing, this._props.tickIncrement);
     }
 
-    constructor(props: any) {
+    constructor(props: IProps) {
         super(props);
         makeObservable(this);
     }
@@ -220,9 +220,7 @@ export class Region extends ObservableReactComponent<IProps> {
             }, 200);
             this._doubleClickEnabled = true;
             document.addEventListener('pointermove', this.onBarPointerMove);
-            document.addEventListener('pointerup', (e: PointerEvent) => {
-                document.removeEventListener('pointermove', this.onBarPointerMove);
-            });
+            document.addEventListener('pointerup', () => document.removeEventListener('pointermove', this.onBarPointerMove));
         }
     };
 
@@ -426,6 +424,7 @@ export class Region extends ObservableReactComponent<IProps> {
                         .map(region => ({ pos: NumCast(region.position), dur: NumCast(region.duration) }))
                         .forEach(({ pos, dur }) => {
                             if (pos !== this.regiondata.position) {
+                                // eslint-disable-next-line no-param-reassign
                                 val += this.regiondata.position;
                                 if (val < 0 || (val > pos && val < pos + dur)) {
                                     cannotMove = true;
@@ -483,7 +482,6 @@ export class Region extends ObservableReactComponent<IProps> {
      * this probably needs biggest change, since everyone expected all keyframes to have a circle (and draggable)
      */
     drawKeyframes = () => {
-        const keyframeDivs: JSX.Element[] = [];
         return DocListCast(this.regiondata.keyframes).map(kf => {
             return (
                 <>
diff --git a/src/client/views/animationtimeline/Track.tsx b/src/client/views/animationtimeline/Track.tsx
index 1e4ed74be..dea7b6aae 100644
--- a/src/client/views/animationtimeline/Track.tsx
+++ b/src/client/views/animationtimeline/Track.tsx
@@ -1,4 +1,4 @@
-import { action, computed, intercept, makeObservable, observable, reaction, runInAction } from 'mobx';
+import { action, computed, intercept, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast, DocListCastAsync, Opt } from '../../../fields/Doc';
@@ -6,7 +6,7 @@ import { Copy } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
 import { listSpec } from '../../../fields/Schema';
-import { Cast, NumCast } from '../../../fields/Types';
+import { Cast, DocCast, NumCast } from '../../../fields/Types';
 import { Transform } from '../../util/Transform';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { Region, RegionData, RegionHelpers } from './Region';
@@ -29,16 +29,16 @@ interface IProps {
 @observer
 export class Track extends ObservableReactComponent<IProps> {
     @observable private _inner = React.createRef<HTMLDivElement>();
-    @observable private _currentBarXReaction: any = undefined;
-    @observable private _timelineVisibleReaction: any = undefined;
-    @observable private _autoKfReaction: any = undefined;
+    @observable private _currentBarXReaction: IReactionDisposer | undefined = undefined;
+    @observable private _timelineVisibleReaction: IReactionDisposer | undefined = undefined;
+    @observable private _autoKfReaction: IReactionDisposer | undefined = undefined;
     @observable private _newKeyframe: boolean = false;
     private readonly MAX_TITLE_HEIGHT = 75;
     @observable private _trackHeight = 0;
     private primitiveWhitelist = ['x', 'y', '_freeform_panX', '_freeform_panY', '_width', '_height', '_rotation', 'opacity', '_layout_scrollTop'];
     private objectWhitelist = ['data'];
 
-    constructor(props: any) {
+    constructor(props: IProps) {
         super(props);
         makeObservable(this);
     }
@@ -101,11 +101,11 @@ export class Track extends ObservableReactComponent<IProps> {
         }
         const keyframes = Cast(this.saveStateRegion.keyframes, listSpec(Doc)) as List<Doc>;
         const kfIndex = keyframes.indexOf(this.saveStateKf);
-        const kf = keyframes[kfIndex] as Doc; //index in the keyframe
+        const kf = DocCast(keyframes[kfIndex]); //index in the keyframe
         if (this._newKeyframe) {
-            DocListCast(this.saveStateRegion.keyframes).forEach((kf, index) => {
-                this.copyDocDataToKeyFrame(kf);
-                kf.opacity = index === 0 || index === 3 ? 0.1 : 1;
+            DocListCast(this.saveStateRegion.keyframes).forEach((keyF, index) => {
+                this.copyDocDataToKeyFrame(keyF);
+                keyF.opacity = index === 0 || index === 3 ? 0.1 : 1;
             });
             this._newKeyframe = false;
         }
@@ -144,7 +144,7 @@ export class Track extends ObservableReactComponent<IProps> {
             () => {
                 return [...this.primitiveWhitelist.map(key => this._props.animatedDoc[key]), ...objects];
             },
-            (changed, reaction) => {
+            (/* changed, reaction */) => {
                 //check for region
                 const region = this.findRegion(this.time);
                 if (region !== undefined) {
@@ -374,7 +374,7 @@ export class Track extends ObservableReactComponent<IProps> {
 
     @action
     copyDocDataToKeyFrame = (doc: Doc) => {
-        var somethingChanged = false;
+        let somethingChanged = false;
         this.primitiveWhitelist.map(key => {
             const originalVal = this._props.animatedDoc[key];
             somethingChanged = somethingChanged || originalVal !== doc[key];
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 1576a8e4a..7f835938b 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -46,7 +46,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
         makeObservable(this);
         CollectionMenu.Instance = this;
         this._canFade = false; // don't let the inking menu fade away
-        this.Pinned = Cast(Doc.UserDoc().menuCollections_pinned, 'boolean', true);
+        this.Pinned = BoolCast(Doc.UserDoc().menuCollections_pinned, true);
         this.jumpTo(300, 300);
     }
 
@@ -269,7 +269,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
         initialize: (button: Doc) => {
             const activeDash = Doc.ActiveDashboard;
             if (activeDash) {
-                button.target_childFilters = (Doc.MySearcher._childFilters || activeDash._childFilters) instanceof ObjectField ? ObjectField.MakeCopy((Doc.MySearcher._childFilters || activeDash._childFilters) as ObjectField) : undefined;
+                button.target_childFilters = (Doc.MySearcher?._childFilters || activeDash._childFilters) instanceof ObjectField ? ObjectField.MakeCopy((Doc.MySearcher?._childFilters || activeDash._childFilters) as ObjectField) : undefined;
                 button.target_searchFilterDocs = activeDash._searchFilterDocs instanceof ObjectField ? ObjectField.MakeCopy(activeDash._searchFilterDocs) : undefined;
             }
         },
diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
index f283b0abe..461689a70 100644
--- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
@@ -155,7 +155,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
     // all docs are added to the column directly to the left.
     @undoBatch
     deleteColumn = () => {
-        const colHdrData = Array.from(Cast(this._props.Doc[this._props.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null));
+        const colHdrData = Array.from(Cast(this._props.Doc[this._props.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), [])!);
         if (this._props.headingObject) {
             //          this._props.docList.forEach(d => (d['$'+this._props.pivotField] = undefined));
             colHdrData.splice(colHdrData.indexOf(this._props.headingObject), 1);
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index eea128803..5a85c8ee3 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -67,7 +67,6 @@ export class CollectionPileView extends CollectionSubView() {
         return (
             <div className="collectionPileView-innards" style={{ pointerEvents: this.contentEvents }}>
                 <CollectionFreeFormView
-                    // eslint-disable-next-line react/jsx-props-no-spreading
                     {...this._props} //
                     layoutEngine={this.layoutEngine}
                     addDocument={this.addPileDoc}
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index fd4bdf364..4e7e19548 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -435,7 +435,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
         anchor['$' + endTag] = anchorEndTime;
         if (addAsAnnotation) {
             if (Cast(dataDoc[fieldKey], listSpec(Doc), null)) {
-                Cast(dataDoc[fieldKey], listSpec(Doc), []).push(anchor);
+                Cast(dataDoc[fieldKey], listSpec(Doc), [])!.push(anchor);
             } else {
                 dataDoc[fieldKey] = new List<Doc>([anchor]);
             }
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 3497a62cb..bee5d016d 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -137,7 +137,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         if (res && de.complete.docDragData) {
             if (this.Document !== Doc.MyRecentlyClosed)
                 de.complete.docDragData.droppedDocuments.forEach(doc => {
-                    if (this.Document !== Doc.MyRecentlyClosed) Doc.RemoveDocFromList(Doc.MyRecentlyClosed, undefined, doc);
+                    if (this.Document !== Doc.MyRecentlyClosed) Doc.MyRecentlyClosed && Doc.RemoveDocFromList(Doc.MyRecentlyClosed, undefined, doc);
                 });
         }
         return res;
@@ -193,7 +193,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         res &&
             doclist.forEach(doc => {
                 Doc.SetContainer(doc, this.Document);
-                if (this.Document !== Doc.MyRecentlyClosed) Doc.RemoveDocFromList(Doc.MyRecentlyClosed, undefined, doc);
+                if (this.Document !== Doc.MyRecentlyClosed) Doc.MyRecentlyClosed && Doc.RemoveDocFromList(Doc.MyRecentlyClosed, undefined, doc);
             });
         return res;
     };
@@ -273,8 +273,8 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         );
     }
     childContextMenuItems = () => {
-        const customScripts = Cast(this.Document.childContextMenuScripts, listSpec(ScriptField), []);
-        const customFilters = Cast(this.Document.childContextMenuFilters, listSpec(ScriptField), []);
+        const customScripts = Cast(this.Document.childContextMenuScripts, listSpec(ScriptField), [])!;
+        const customFilters = Cast(this.Document.childContextMenuFilters, listSpec(ScriptField), [])!;
         const icons = StrListCast(this.Document.childContextMenuIcons);
         return StrListCast(this.Document.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label }));
     };
diff --git a/src/client/views/collections/FlashcardPracticeUI.tsx b/src/client/views/collections/FlashcardPracticeUI.tsx
index f24a8acb7..2f46c00bd 100644
--- a/src/client/views/collections/FlashcardPracticeUI.tsx
+++ b/src/client/views/collections/FlashcardPracticeUI.tsx
@@ -60,7 +60,7 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp
 
     get practiceField() { return this._props.fieldKey + "_practice"; } // prettier-ignore
 
-    @computed get filterDoc() { return DocListCast(Doc.MyContextMenuBtns.data).find(doc => doc.title === 'Filter'); } // prettier-ignore
+    @computed get filterDoc() { return DocListCast(Doc.MyContextMenuBtns?.data).find(doc => doc.title === 'Filter'); } // prettier-ignore
     @computed get practiceMode() {  return this._props.allChildDocs().some(doc => doc._layout_flashcardType) ? StrCast(this._props.layoutDoc.practiceMode) : ''; } // prettier-ignore
 
     btnHeight = () => NumCast(this.filterDoc?.height);
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 4beb75074..fb2d0955f 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -70,7 +70,7 @@ export interface TreeViewProps {
     treeViewHideHeaderFields: () => boolean;
     renderedIds: string[]; // list of document ids rendered used to avoid unending expansion of items in a cycle
     onCheckedClick?: () => ScriptField;
-    onChildClick?: () => ScriptField;
+    onChildClick?: () => ScriptField | undefined;
     skipFields?: string[];
     firstLevel: boolean;
     // TODO: [AL] add these
@@ -1268,7 +1268,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         treeViewHideHeaderFields: () => boolean,
         renderedIds: string[],
         onCheckedClick: undefined | (() => ScriptField),
-        onChildClick: undefined | (() => ScriptField),
+        onChildClick: undefined | (() => ScriptField | undefined),
         skipFields: string[] | undefined,
         firstLevel: boolean,
         whenChildContentsActiveChanged: (isActive: boolean) => void,
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 158bac7ba..4d17dedfb 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -108,7 +108,7 @@ export function computePassLayout(poolData: Map<string, PoolData>, pivotDoc: Doc
 }
 
 function toNumber(val: FieldResult<FieldType>) {
-    return val === undefined ? undefined : DateCast(val) ? DateCast(val).date.getTime() : 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 */) {
diff --git a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx
index 72485aa86..ad05a798b 100644
--- a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx
+++ b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx
@@ -106,7 +106,7 @@ export class UniqueFaceBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
                     // assign the face in the image that's closest to the face collection's face
                     if (faceAnno) {
-                        faceAnno.face && FaceRecognitionHandler.UniqueFaceRemoveFaceImage(faceAnno, DocCast(faceAnno.face));
+                        DocCast(faceAnno.face) && FaceRecognitionHandler.UniqueFaceRemoveFaceImage(faceAnno, DocCast(faceAnno.face)!);
                         FaceRecognitionHandler.UniqueFaceAddFaceImage(faceAnno, this.Document);
                         faceAnno.$face = this.Document[DocData];
                     }
@@ -116,7 +116,7 @@ export class UniqueFaceBox extends ViewBoxBaseComponent<FieldViewProps>() {
             ?.filter(doc => DocCast(doc.face)?.type === DocumentType.UFACE)
             .forEach(faceAnno => {
                 const imgDoc = faceAnno;
-                faceAnno.face && FaceRecognitionHandler.UniqueFaceRemoveFaceImage(imgDoc, DocCast(faceAnno.face));
+                DocCast(faceAnno.face) && FaceRecognitionHandler.UniqueFaceRemoveFaceImage(imgDoc, DocCast(faceAnno.face)!);
                 FaceRecognitionHandler.UniqueFaceAddFaceImage(faceAnno, this.Document);
                 faceAnno.$face = this.Document[DocData];
             });
@@ -187,7 +187,7 @@ export class UniqueFaceBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             ele?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
                         })}>
                         {FaceRecognitionHandler.UniqueFaceImages(this.Document).map((doc, i) => {
-                            const [name, type] = ImageCastToNameType(doc[Doc.LayoutDataKey(doc)]) ?? ['-missing-', '.png'];
+                            const [name, type] = ImageCastToNameType(doc?.[Doc.LayoutDataKey(doc)]) ?? ['-missing-', '.png'];
                             return (
                                 <div
                                     className="image-wrapper"
@@ -197,7 +197,7 @@ export class UniqueFaceBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                             this,
                                             e,
                                             () => {
-                                                const dragDoc = DocListCast(doc.data_annotations).find(a => a.face === this.Document[DocData]) ?? this.Document;
+                                                const dragDoc = DocListCast(doc?.data_annotations).find(a => a.face === this.Document[DocData]) ?? this.Document;
                                                 DragManager.StartDocumentDrag([e.target as HTMLElement], new DragManager.DocumentDragData([dragDoc], dropActionType.embed), e.clientX, e.clientY);
                                                 return true;
                                             },
@@ -205,7 +205,7 @@ export class UniqueFaceBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                             emptyFunction
                                         )
                                     }>
-                                    <img onClick={() => DocumentView.showDocument(doc, { willZoomCentered: true })} style={{ maxWidth: '60px', margin: '10px' }} src={`${name}_o.${type}`} />
+                                    <img onClick={() => doc && DocumentView.showDocument(doc, { willZoomCentered: true })} style={{ maxWidth: '60px', margin: '10px' }} src={`${name}_o.${type}`} />
                                     <div className="remove-item">
                                         <IconButton tooltip={'Remove Doc From Face Collection'} onPointerDown={() => this.removeFaceImageFromUniqueFace(doc)} icon={'x'} style={{ width: '4px' }} size={Size.XSMALL} />
                                     </div>
@@ -239,11 +239,12 @@ export class FaceCollectionBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 
     moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => !!(this._props.removeDocument?.(doc) && addDocument?.(doc));
+    // eslint-disable-next-line @typescript-eslint/no-unused-vars
     addDocument = (doc: Doc | Doc[], annotationKey?: string) => {
         const uniqueFaceDoc = doc instanceof Doc ? doc : doc[0];
         const added = uniqueFaceDoc.type === DocumentType.UFACE;
         if (added) {
-            Doc.SetContainer(uniqueFaceDoc, Doc.MyFaceCollection);
+            Doc.MyFaceCollection && Doc.SetContainer(uniqueFaceDoc, Doc.MyFaceCollection);
             Doc.ActiveDashboard && Doc.AddDocToList(Doc.ActiveDashboard[DocData], 'myUniqueFaces', uniqueFaceDoc);
         }
         return added;
diff --git a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
index 81a2d8e64..16d33eb93 100644
--- a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
@@ -107,7 +107,7 @@ export class SchemaColumnHeader extends ObservableReactComponent<SchemaColumnHea
             focus: emptyFunction,
             addDocTab: SchemaTableCell.addFieldDoc,
             pinToPres: returnZero,
-            Document: DocCast(Document.rootDocument, Document),
+            Document: DocCast(Document.rootDocument, Document)!,
             fieldKey: fieldKey,
             PanelWidth: columnWidth,
             PanelHeight: props.rowHeight,
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 8e1edc1ee..8b34b4139 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -106,7 +106,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
         const { Doc: Document, fieldKey, /* getFinfo,*/ columnWidth, isRowActive } = props;
         let protoCount = 0;
         const layoutDoc = fieldKey.startsWith('_') ? Document[DocLayout] : Document;
-        let doc = Document;
+        let doc: Doc | undefined = Document;
         while (doc) {
             if (Object.keys(doc).includes(fieldKey.replace(/^_/, ''))) break;
             protoCount++;
@@ -168,9 +168,11 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
         while ((matches = idPattern.exec(field)) !== null) {
             results.push([matches[0], matches[1].replace(/"/g, '')]);
         }
-        results.forEach(idFuncPair => {
-            modField = modField.replace(idFuncPair[0], 'd' + DocumentView.getDocViewIndex(IdToDoc(idFuncPair[1])).toString());
-        });
+        results
+            .filter(idFuncPair => IdToDoc(idFuncPair[1]))
+            .forEach(idFuncPair => {
+                modField = modField.replace(idFuncPair[0], 'd' + DocumentView.getDocViewIndex(IdToDoc(idFuncPair[1])!).toString());
+            });
 
         if (modField.endsWith(';')) modField = modField.substring(0, modField.length - 1);
 
@@ -328,7 +330,7 @@ export class SchemaImageCell extends ObservableReactComponent<SchemaTableCellPro
         const altpaths = alts
             .map(doc => Cast(doc[Doc.LayoutDataKey(doc)], ImageField, null)?.url)
             .filter(url => url)
-            .map(url => this.choosePath(url)); // access the primary layout data of the alternate documents
+            .map(url => this.choosePath(url!)); // access the primary layout data of the alternate documents
         const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths;
         // If there is a path, follow it; otherwise, follow a link to a default image icon
         const url = paths.length ? paths : [ClientUtils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png')];
@@ -379,7 +381,7 @@ export class SchemaDateCell extends ObservableReactComponent<SchemaTableCellProp
     }
 
     @observable _pickingDate: boolean = false;
-    @computed get date(): DateField {
+    @computed get date(): DateField | undefined {
         // if the cell is a date field, cast then contents to a date. Otherrwwise, make the contents undefined.
         return DateCast(this._props.Doc[this._props.fieldKey]);
     }
@@ -399,7 +401,7 @@ export class SchemaDateCell extends ObservableReactComponent<SchemaTableCellProp
         return (
             <>
                 <div style={{ pointerEvents: 'none' }} tabIndex={1}>
-                    <DatePicker dateFormat="Pp" selected={this.date?.date ?? Date.now()} onChange={emptyFunction} />
+                    <DatePicker dateFormat="Pp" selected={this.date?.date ?? new Date()} onChange={emptyFunction} />
                 </div>
                 {pointerEvents === 'none' || !selectedCell(this._props) ? null : (
                     <Popup
@@ -410,7 +412,7 @@ export class SchemaDateCell extends ObservableReactComponent<SchemaTableCellProp
                         background={SnappingManager.userBackgroundColor}
                         popup={
                             <div style={{ width: 'fit-content', height: '200px' }}>
-                                <DatePicker open dateFormat="Pp" selected={this.date?.date ?? Date.now()} onChange={this.handleChange} />
+                                <DatePicker open dateFormat="Pp" selected={this.date?.date ?? new Date()} onChange={this.handleChange} />
                             </div>
                         }
                     />
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index c15508669..9eaf0e491 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -1,10 +1,9 @@
-/* eslint-disable react/require-default-props */
 import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, StrListCast } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
-import { Cast, DocCast } from '../../../fields/Types';
+import { DocCast } from '../../../fields/Types';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { DocumentView } from '../nodes/DocumentView';
 import './LinkMenu.scss';
@@ -56,7 +55,7 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
                   ? this.props.docView._props.LayoutTemplateString?.includes('link_anchor_1')
                       ? DocCast(linkDoc.link_anchor_2)
                       : DocCast(linkDoc.link_anchor_1)
-                  : Doc.getOppositeAnchor(linkDoc, sourceDoc) || Doc.getOppositeAnchor(linkDoc, Cast(linkDoc.link_anchor_2, Doc, null)?.annotationOn === sourceDoc ? Cast(linkDoc.link_anchor_2, Doc, null) : Cast(linkDoc.link_anchor_1, Doc, null));
+                  : Doc.getOppositeAnchor(linkDoc, sourceDoc) || Doc.getOppositeAnchor(linkDoc, DocCast(linkDoc.link_anchor_2)?.annotationOn === sourceDoc ? DocCast(linkDoc.link_anchor_2) : DocCast(linkDoc.link_anchor_1));
             return !destDoc || !sourceDoc ? null : (
                 <LinkMenuItem
                     key={linkDoc[Id]}
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 1c92c3b0d..c984a7053 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -68,8 +68,8 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
     @computed get sourceAnchor() {
         const ldoc = this._props.linkDoc;
         if (this._props.sourceDoc !== ldoc.link_anchor_1 && this._props.sourceDoc !== ldoc.link_anchor_2) {
-            if (Doc.AreProtosEqual(DocCast(DocCast(ldoc.link_anchor_1).annotationOn), this._props.sourceDoc)) return DocCast(ldoc.link_anchor_1);
-            if (Doc.AreProtosEqual(DocCast(DocCast(ldoc.link_anchor_2).annotationOn), this._props.sourceDoc)) return DocCast(ldoc.link_anchor_2);
+            if (Doc.AreProtosEqual(DocCast(DocCast(ldoc.link_anchor_1)?.annotationOn), this._props.sourceDoc)) return DocCast(ldoc.link_anchor_1);
+            if (Doc.AreProtosEqual(DocCast(DocCast(ldoc.link_anchor_2)?.annotationOn), this._props.sourceDoc)) return DocCast(ldoc.link_anchor_2);
         }
         return this._props.sourceDoc;
     }
diff --git a/src/client/views/newlightbox/ExploreView/ExploreView.tsx b/src/client/views/newlightbox/ExploreView/ExploreView.tsx
index f8c07cc43..7f9a13549 100644
--- a/src/client/views/newlightbox/ExploreView/ExploreView.tsx
+++ b/src/client/views/newlightbox/ExploreView/ExploreView.tsx
@@ -1,5 +1,3 @@
-/* eslint-disable jsx-a11y/no-static-element-interactions */
-/* eslint-disable jsx-a11y/click-events-have-key-events */
 import * as React from 'react';
 import { StrCast } from '../../../../fields/Types';
 import { NewLightboxView } from '../NewLightboxView';
@@ -19,7 +17,7 @@ export function ExploreView(props: IExploreView) {
                         const x = (rec.embedding.x / xBound) * 50;
                         const y = (rec.embedding.y / yBound) * 50;
                         return (
-                            <div className="exploreView-doc" onClick={() => {}} style={{ top: `calc(50% + ${y}%)`, left: `calc(50% + ${x}%)` }}>
+                            <div key={'' + x + ' ' + y} className="exploreView-doc" onClick={() => {}} style={{ top: `calc(50% + ${y}%)`, left: `calc(50% + ${x}%)` }}>
                                 {rec.title}
                             </div>
                         );
diff --git a/src/client/views/newlightbox/NewLightboxView.tsx b/src/client/views/newlightbox/NewLightboxView.tsx
index 87cef01d2..7ec45d0dd 100644
--- a/src/client/views/newlightbox/NewLightboxView.tsx
+++ b/src/client/views/newlightbox/NewLightboxView.tsx
@@ -5,7 +5,7 @@ import { returnEmptyFilter, returnTrue } from '../../../ClientUtils';
 import { emptyFunction } from '../../../Utils';
 import { CreateLinkToActiveAudio, Doc, DocListCast, Opt, returnEmptyDoclist } from '../../../fields/Doc';
 import { InkTool } from '../../../fields/InkField';
-import { Cast, NumCast, StrCast, toList } from '../../../fields/Types';
+import { Cast, DocCast, NumCast, StrCast, toList } from '../../../fields/Types';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
 import { GestureOverlay } from '../GestureOverlay';
@@ -100,7 +100,7 @@ export class NewLightboxView extends React.Component<LightboxViewProps> {
             SnappingManager.SetExploreMode(false);
         } else {
             const l = CreateLinkToActiveAudio(() => doc).lastElement();
-            l && (Cast(l.link_anchor_2, Doc, null).backgroundColor = 'lightgreen');
+            DocCast(l?.link_anchor_2) && (DocCast(l!.link_anchor_2)!.backgroundColor = 'lightgreen');
             DocumentView.CurrentlyPlaying?.forEach(dv => dv.ComponentView?.Pause?.());
             // DocumentView.PinDoc(doc, { hidePresBox: true });
             this._history ? this._history.push({ doc, target }) : (this._history = [{ doc, target }]);
@@ -142,7 +142,7 @@ export class NewLightboxView extends React.Component<LightboxViewProps> {
         const targetDocView = target && DocumentView.getLightboxDocumentView(target);
         if (targetDocView && target) {
             const l = CreateLinkToActiveAudio(() => targetDocView.ComponentView?.getAnchor?.(true) || target).lastElement();
-            l && (Cast(l.link_anchor_2, Doc, null).backgroundColor = 'lightgreen');
+            DocCast(l?.link_anchor_2) && (DocCast(l!.link_anchor_2)!.backgroundColor = 'lightgreen');
             DocumentView.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 });
             if (NewLightboxView._history?.lastElement().target !== target) NewLightboxView._history?.push({ doc, target });
         } else if (!target && NewLightboxView.path.length) {
@@ -326,7 +326,6 @@ export class NewLightboxView extends React.Component<LightboxViewProps> {
 }
 interface NewLightboxTourBtnProps {
     navBtn: (left: Opt<string | number>, bottom: Opt<number>, top: number, icon: string, display: () => string, click: (e: React.MouseEvent) => void, color?: string) => JSX.Element;
-    // eslint-disable-next-line react/no-unused-prop-types
     future: () => Opt<Doc[]>;
     stepInto: () => void;
 }
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index c80e8ebc1..9369ff98a 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -108,7 +108,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     // all CSV records in the dataset (that aren't an empty row)
     @computed.struct get records() {
         try {
-            const records = DataVizBox.dataset.get(CsvCast(this.dataDoc[this.fieldKey]).url.href);
+            const records = DataVizBox.dataset.get(CsvCast(this.dataDoc[this.fieldKey])?.url.href ?? '');
             this._urlError = false;
             return records?.filter(record => Object.keys(record).some(key => record[key])) ?? [];
         } catch {
@@ -344,7 +344,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     componentDidMount() {
         this._props.setContentViewBox?.(this);
         if (!this._urlError) {
-            if (!DataVizBox.dataset.has(CsvCast(this.dataDoc[this.fieldKey]).url.href)) this.fetchData();
+            if (!DataVizBox.dataset.has(CsvCast(this.dataDoc[this.fieldKey])?.url.href ?? '')) this.fetchData();
         }
         this._disposers.datavis = reaction(
             () => {
@@ -386,8 +386,8 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                     this.layoutDoc._dataViz_schemaOG = loading;
                 }
                 const ogDoc = this.layoutDoc._dataViz_schemaOG as Doc;
-                const ogHref = CsvCast(ogDoc[this.fieldKey]) ? CsvCast(ogDoc[this.fieldKey]).url.href : undefined;
-                const { href } = CsvCast(this.Document[this.fieldKey]).url;
+                const ogHref = CsvCast(ogDoc[this.fieldKey]) ? CsvCast(ogDoc[this.fieldKey])!.url.href : undefined;
+                const { href } = CsvCast(this.Document[this.fieldKey])?.url ?? { href: '' };
                 if (ogHref && !DataVizBox.datasetSchemaOG.has(href)) {
                     // sets original dataset to the var
                     const lastRow = current.pop();
@@ -399,7 +399,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             },
             current => {
                 if (current) {
-                    const { href } = CsvCast(this.Document[this.fieldKey]).url;
+                    const { href } = CsvCast(this.Document[this.fieldKey])?.url ?? { href: '' };
                     if (this.layoutDoc.dataViz_schemaLive) DataVizBox.dataset.set(href, current);
                     else DataVizBox.dataset.set(href, DataVizBox.datasetSchemaOG.get(href)!);
                 }
@@ -414,9 +414,9 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     fetchData = () => {
         if (!this.Document.dataViz_asSchema) {
-            DataVizBox.dataset.set(CsvCast(this.dataDoc[this.fieldKey]).url.href, []); // assign temporary dataset as a lock to prevent duplicate server requests
+            DataVizBox.dataset.set(CsvCast(this.dataDoc[this.fieldKey])?.url.href ?? '', []); // assign temporary dataset as a lock to prevent duplicate server requests
             fetch('/csvData?uri=' + (this.dataUrl?.url.href ?? '')) //
-                .then(res => res.json().then(action(jsonRes => !jsonRes.errno && DataVizBox.dataset.set(CsvCast(this.dataDoc[this.fieldKey]).url.href, jsonRes))));
+                .then(res => res.json().then(action(jsonRes => !jsonRes.errno && DataVizBox.dataset.set(CsvCast(this.dataDoc[this.fieldKey])?.url.href ?? '', jsonRes))));
         }
     };
 
@@ -523,7 +523,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         GPTPopup.Instance.createFilteredDoc = this.createFilteredDoc;
         GPTPopup.Instance.setDataJson('');
         GPTPopup.Instance.setMode(GPTPopupMode.DATA);
-        const csvdata = DataVizBox.dataset.get(CsvCast(this.dataDoc[this.fieldKey]).url.href);
+        const csvdata = DataVizBox.dataset.get(CsvCast(this.dataDoc[this.fieldKey])?.url.href ?? '');
         GPTPopup.Instance.setDataJson(JSON.stringify(csvdata));
         GPTPopup.Instance.generateDataAnalysis();
     });
diff --git a/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx b/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx
index 8ae29a88c..1e2a95d31 100644
--- a/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx
+++ b/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx
@@ -11,10 +11,8 @@ import { DragManager } from '../../../util/DragManager';
 import { DocumentView } from '../DocumentView';
 import './SchemaCSVPopUp.scss';
 
-interface SchemaCSVPopUpProps {}
-
 @observer
-export class SchemaCSVPopUp extends React.Component<SchemaCSVPopUpProps> {
+export class SchemaCSVPopUp extends React.Component<object> {
     // eslint-disable-next-line no-use-before-define
     static Instance: SchemaCSVPopUp;
 
@@ -23,7 +21,7 @@ export class SchemaCSVPopUp extends React.Component<SchemaCSVPopUpProps> {
     @observable public target: Doc | undefined = undefined;
     @observable public visible: boolean = false;
 
-    constructor(props: SchemaCSVPopUpProps) {
+    constructor(props: object) {
         super(props);
         makeObservable(this);
         SchemaCSVPopUp.Instance = this;
diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
index 5450d03b1..a7c4a00b0 100644
--- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx
+++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
@@ -5,10 +5,9 @@ import { IReactionDisposer, action, computed, makeObservable, observable, reacti
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { FaFillDrip } from 'react-icons/fa';
-import { Doc, NumListCast } from '../../../../../fields/Doc';
+import { Doc, NumListCast, StrListCast } from '../../../../../fields/Doc';
 import { List } from '../../../../../fields/List';
-import { listSpec } from '../../../../../fields/Schema';
-import { Cast, DocCast, StrCast } from '../../../../../fields/Types';
+import { DocCast, StrCast } from '../../../../../fields/Types';
 import { Docs } from '../../../../documents/Documents';
 import { undoable } from '../../../../util/UndoManager';
 import { ObservableReactComponent } from '../../../ObservableReactComponent';
@@ -108,13 +107,13 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
     }
 
     @computed get defaultBarColor() {
-        return Cast(this.layoutDoc.dataViz_histogram_defaultColor, 'string', '#69b3a2');
+        return StrCast(this.layoutDoc.dataViz_histogram_defaultColor, '#69b3a2')!;
     }
     @computed get barColors() {
-        return Cast(this.layoutDoc.dataViz_histogram_barColors, listSpec('string'), null);
+        return StrListCast(this.layoutDoc.dataViz_histogram_barColors);
     }
     @computed get selectedBins() {
-        return Cast(this.layoutDoc.dataViz_histogram_selectedBins, listSpec('number'), null);
+        return NumListCast(this.layoutDoc.dataViz_histogram_selectedBins);
     }
 
     @computed get rangeVals(): { xMin?: number; xMax?: number; yMin?: number; yMax?: number } {
@@ -129,7 +128,7 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
         // restore selected bars
         this._histogramSvg?.selectAll('rect').attr('class', dIn => {
             const d = dIn as HistogramData;
-            if (this.selectedBins.some(selBin => d[0] === selBin)) {
+            if (this.selectedBins?.some(selBin => d[0] === selBin)) {
                 this._selectedBars.push(d);
                 return 'histogram-bar hover';
             }
@@ -199,10 +198,10 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
                     const alreadySelected = this._selectedBars.findIndex(eachData => !Object.keys(d).some(key => d[key] !== eachData[key]));
                     if (alreadySelected !== -1) {
                         this._selectedBars.splice(alreadySelected, 1);
-                        this.selectedBins.splice(alreadySelected, 1);
+                        this.selectedBins?.splice(alreadySelected, 1);
                     } else {
                         this._selectedBars.push(d);
-                        this.selectedBins.push(d[0] as number);
+                        this.selectedBins?.push(d[0] as number);
                     }
                     const showSelectedLabel = (dataset: HistogramData[]) => {
                         const datum = dataset.lastElement();
diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
index 6b047546c..80fadf178 100644
--- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
@@ -26,7 +26,7 @@ export interface LineChartProps {
     layoutDoc: Doc;
     axes: string[];
     titleCol: string;
-    records: { [key: string]: any }[];
+    records: { [key: string]: string }[];
     width: number;
     height: number;
     dataDoc: Doc;
@@ -47,7 +47,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
     @observable _currSelected: DataPoint | undefined = undefined;
 
     // TODO: nda - some sort of mapping that keeps track of the annotated points so we can easily remove when annotations list updates
-    constructor(props: any) {
+    constructor(props: LineChartProps) {
         super(props);
         makeObservable(this);
     }
@@ -79,7 +79,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
     @computed get incomingHighlited() {
         // return selected x and y axes
         // otherwise, use the selection of whatever is linked to us
-        const incomingVizBox = DocumentView.getFirstDocumentView(this.parentViz)?.ComponentView as DataVizBox;
+        const incomingVizBox = this.parentViz && (DocumentView.getFirstDocumentView(this.parentViz)?.ComponentView as DataVizBox);
         const highlitedRowIds = NumListCast(incomingVizBox?.layoutDoc?.dataViz_highlitedRows);
         return this._tableData.filter((record, i) => highlitedRowIds.includes(this._tableDataIds[i])); // get all the datapoints they have selected field set by incoming anchor
     }
@@ -170,8 +170,8 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
             }
         });
         if (!ptWasSelected) {
-            selectedDatapoints.push(d.x + ',' + d.y);
-            this._currSelected = selectedDatapoints.length > 1 ? undefined : d;
+            selectedDatapoints?.push(d.x + ',' + d.y);
+            this._currSelected = (selectedDatapoints?.length ?? 0 > 1) ? undefined : d;
         }
 
         // for filtering child dataviz docs
@@ -190,7 +190,14 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
         }
     }
 
-    drawDataPoints(data: DataPoint[], idx: number, xScale: d3.ScaleLinear<number, number, never>, yScale: d3.ScaleLinear<number, number, never>, higlightFocusPt: any, tooltip: any) {
+    drawDataPoints(
+        data: DataPoint[], //
+        idx: number,
+        xScale: d3.ScaleLinear<number, number, never>,
+        yScale: d3.ScaleLinear<number, number, never>,
+        higlightFocusPt: d3.Selection<SVGGElement, unknown, null, undefined>,
+        tooltip: d3.Selection<HTMLDivElement, unknown, null, undefined>
+    ) {
         if (this._lineChartSvg) {
             const circleClass = '.circle-' + idx;
             this._lineChartSvg
@@ -211,7 +218,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
                 .on('mouseleave', () => {
                     tooltip?.transition().duration(300).style('opacity', 0);
                 })
-                .on('click', (e: any) => {
+                .on('click', e => {
                     const d0 = { x: Number(e.target.getAttribute('data-x')), y: Number(e.target.getAttribute('data-y')) };
                     // find .circle-d1 with data-x = d0.x and data-y = d0.y
                     this.setCurrSelected(d0);
@@ -220,7 +227,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
         }
     }
 
-    drawChart = (dataSet: any[][], rangeVals: { xMin?: number; xMax?: number; yMin?: number; yMax?: number }, width: number, height: number) => {
+    drawChart = (dataSet: { x: number; y: number }[][], rangeVals: { xMin?: number; xMax?: number; yMin?: number; yMax?: number }, width: number, height: number) => {
         // clearing tooltip and the current chart
         d3.select(this._lineChartRef).select('svg').remove();
         d3.select(this._lineChartRef).select('.tooltip').remove();
@@ -277,7 +284,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
             svg.append('path').attr('stroke', 'red');
 
             // legend
-            const color: any = d3.scaleOrdinal().range(['black', 'blue']).domain([this._props.axes[1], this._props.axes[2]]);
+            const color = d3.scaleOrdinal().range(['black', 'blue']).domain([this._props.axes[1], this._props.axes[2]]);
             svg.selectAll('mydots')
                 .data([this._props.axes[1], this._props.axes[2]])
                 .enter()
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index b73123691..ad2731109 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -128,7 +128,7 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
             selected.splice(selected.indexOf(rowId), 1);
         } else selected?.push(rowId);
         e.stopPropagation();
-        this.hasRowsToFilter = selected.length > 0;
+        this.hasRowsToFilter = (selected?.length ?? 0) > 0;
     };
 
     columnPointerDown = (e: React.PointerEvent, col: string) => {
diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx
index dcc6e27ed..3cacb6692 100644
--- a/src/client/views/nodes/EquationBox.tsx
+++ b/src/client/views/nodes/EquationBox.tsx
@@ -1,7 +1,6 @@
 import { action, computed, makeObservable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Doc } from '../../../fields/Doc';
 import { NumCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
 import { DocUtils } from '../../documents/DocUtils';
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 34e3a5d76..a6872f8dc 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -6,16 +6,17 @@ import { DateField } from '../../../fields/DateField';
 import { Doc, Field, FieldType, Opt } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
 import { ScriptField } from '../../../fields/ScriptField';
+import { WebField } from '../../../fields/URLField';
+import { DragManager } from '../../util/DragManager';
 import { dropActionType } from '../../util/DropActionTypes';
 import { Transform } from '../../util/Transform';
+import { ContextMenuProps } from '../ContextMenuItem';
 import { PinProps } from '../PinFuncs';
 import { ViewBoxInterface } from '../ViewBoxInterface';
 import { DocumentView } from './DocumentView';
 import { FocusViewOptions } from './FocusViewOptions';
-import { OpenWhere } from './OpenWhere';
-import { WebField } from '../../../fields/URLField';
-import { ContextMenuProps } from '../ContextMenuItem';
 import { FormattedTextBox } from './formattedText/FormattedTextBox';
+import { OpenWhere } from './OpenWhere';
 
 export type FocusFuncType = (doc: Doc, options: FocusViewOptions) => Opt<number>;
 export type StyleProviderFuncType = (
diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx
index 91c351895..8e4b64851 100644
--- a/src/client/views/nodes/FunctionPlotBox.tsx
+++ b/src/client/views/nodes/FunctionPlotBox.tsx
@@ -2,10 +2,11 @@ import functionPlot, { Chart } from 'function-plot';
 import { action, computed, makeObservable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Doc, DocListCast } from '../../../fields/Doc';
+import { returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
+import { Doc, DocListCast, NumListCast } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
-import { listSpec } from '../../../fields/Schema';
-import { Cast, StrCast } from '../../../fields/Types';
+import { StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
 import { DocUtils } from '../../documents/DocUtils';
 import { DocumentType } from '../../documents/DocumentTypes';
@@ -15,8 +16,6 @@ import { undoBatch } from '../../util/UndoManager';
 import { ViewBoxAnnotatableComponent } from '../DocComponent';
 import { PinDocView, PinProps } from '../PinFuncs';
 import { FieldView, FieldViewProps } from './FieldView';
-import { returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
-import { emptyFunction } from '../../../Utils';
 
 @observer
 export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@@ -76,7 +75,7 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
         this._plotEle = ele || this._plotEle;
         const width = this._props.PanelWidth();
         const height = this._props.PanelHeight();
-        const xrange = Cast(this.layoutDoc.xRange, listSpec('number'), [-10, 10]);
+        const xrange = NumListCast(this.layoutDoc.xRange, [-10, 10])!;
         try {
             this._plotEle?.children.length && this._plotEle.removeChild(this._plotEle.children[0]);
             this._plot = functionPlot({
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 9c6b8e99c..999d7089b 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -212,7 +212,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     drop = undoable(
         action((e: Event, de: DragManager.DropEvent) => {
-            if (de.complete.docDragData && this._props.rejectDrop?.(de.complete.docDragData?.draggedDocuments, this.DocumentView?.())) {
+            if (de.complete.docDragData && this._props.rejectDrop?.(de, this.DocumentView?.())) {
                 let added: boolean | undefined;
                 const hitDropTarget = (ele: HTMLElement, dropTarget: HTMLDivElement | null): boolean => {
                     if (!ele) return false;
diff --git a/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx b/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx
index 8784a709a..8bb7ddd9f 100644
--- a/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx
+++ b/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx
@@ -43,7 +43,7 @@ export class DirectionsAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
         return this._left > 0;
     }
 
-    constructor(props: Readonly<{}>) {
+    constructor(props: AntimodeMenuProps) {
         super(props);
 
         DirectionsAnchorMenu.Instance = this;
diff --git a/src/client/views/nodes/calendarBox/CalendarBox.tsx b/src/client/views/nodes/calendarBox/CalendarBox.tsx
index 6f1f58a4c..fa863e123 100644
--- a/src/client/views/nodes/calendarBox/CalendarBox.tsx
+++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx
@@ -105,9 +105,10 @@ export class CalendarBox extends CollectionSubView() {
 
     // TODO: Return a different color based on the event type
     eventToColor = (event: Doc): string => {
-        return 'red';
+        return 'red' + event;
     };
 
+    // eslint-disable-next-line @typescript-eslint/no-unused-vars
     internalDocDrop = (e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData) => {
         if (!super.onInternalDrop(e, de)) return false;
         de.complete.docDragData?.droppedDocuments.forEach(doc => {
diff --git a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx
index 528bcd05a..6c3da8977 100644
--- a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx
+++ b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx
@@ -196,7 +196,7 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
             // Add CSV details to linked files
             this._linked_csv_files.push({
-                filename: CsvCast(newLinkedDoc.data).url.pathname,
+                filename: CsvCast(newLinkedDoc.data)?.url.pathname ?? '',
                 id: csvId,
                 text: csvData,
             });
@@ -634,6 +634,7 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     };
 
     getDirectMatchingSegmentStart = (doc: Doc, citationText: string, indexesOfSegments: string[]): number => {
+        // eslint-disable-next-line @typescript-eslint/no-explicit-any
         const originalSegments = JSON.parse(StrCast(doc.original_segments!)).map((segment: any, index: number) => ({
             index: index.toString(),
             text: segment.text,
@@ -877,7 +878,8 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         return LinkManager.Instance.getAllRelatedLinks(this.Document)
             .map(d => DocCast(LinkManager.getOppositeAnchor(d, this.Document)))
             .map(d => DocCast(d?.annotationOn, d))
-            .filter(d => d);
+            .filter(d => d)
+            .map(d => d!);
     }
 
     /**
@@ -889,6 +891,7 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             .map(d => DocCast(LinkManager.getOppositeAnchor(d, this.Document)))
             .map(d => DocCast(d?.annotationOn, d))
             .filter(d => d)
+            .map(d => d!)
             .filter(d => {
                 console.log(d.ai_doc_id);
                 return d.ai_doc_id;
@@ -905,15 +908,14 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             LinkManager.Instance.getAllRelatedLinks(this.Document)
                 .map(d => DocCast(LinkManager.getOppositeAnchor(d, this.Document)))
                 .map(d => DocCast(d?.annotationOn, d))
-                .filter(d => d)
-                .filter(d => d.summary)
+                .filter(d => d?.summary)
                 .map((doc, index) => {
-                    if (PDFCast(doc.data)) {
-                        return `<summary file_name="${PDFCast(doc.data).url.pathname}" applicable_tools=["rag"]>${doc.summary}</summary>`;
-                    } else if (CsvCast(doc.data)) {
-                        return `<summary file_name="${CsvCast(doc.data).url.pathname}" applicable_tools=["dataAnalysis"]>${doc.summary}</summary>`;
+                    if (PDFCast(doc?.data)) {
+                        return `<summary file_name="${PDFCast(doc!.data)!.url.pathname}" applicable_tools=["rag"]>${doc!.summary}</summary>`;
+                    } else if (CsvCast(doc?.data)) {
+                        return `<summary file_name="${CsvCast(doc!.data)!.url.pathname}" applicable_tools=["dataAnalysis"]>${doc!.summary}</summary>`;
                     } else {
-                        return `${index + 1}) ${doc.summary}`;
+                        return `${index + 1}) ${doc?.summary}`;
                     }
                 })
                 .join('\n') + '\n'
diff --git a/src/client/views/nodes/chatbot/tools/GetDocsTool.ts b/src/client/views/nodes/chatbot/tools/GetDocsTool.ts
index 05482a66e..42a7747d3 100644
--- a/src/client/views/nodes/chatbot/tools/GetDocsTool.ts
+++ b/src/client/views/nodes/chatbot/tools/GetDocsTool.ts
@@ -40,7 +40,10 @@ export class GetDocsTool extends BaseTool<GetDocsToolParamsType> {
     }
 
     async execute(args: ParametersType<GetDocsToolParamsType>): Promise<Observation[]> {
-        const docs = args.document_ids.map(doc_id => DocCast(DocServer.GetCachedRefField(doc_id)));
+        const docs = args.document_ids
+            .map(doc_id => DocCast(DocServer.GetCachedRefField(doc_id)))
+            .filter(d => d)
+            .map(d => d!);
         const collection = Docs.Create.FreeformDocument(docs, { title: args.title });
         this._docView._props.addDocTab(collection, OpenWhere.addRight);
         return [{ type: 'text', text: `Collection created in Dash called ${args.title}` }];
diff --git a/src/client/views/nodes/chatbot/vectorstore/Vectorstore.ts b/src/client/views/nodes/chatbot/vectorstore/Vectorstore.ts
index c1915a398..6d524e40f 100644
--- a/src/client/views/nodes/chatbot/vectorstore/Vectorstore.ts
+++ b/src/client/views/nodes/chatbot/vectorstore/Vectorstore.ts
@@ -15,7 +15,6 @@ import { Networking } from '../../../../Network';
 import { AI_Document, CHUNK_TYPE, RAGChunk } from '../types/types';
 import OpenAI from 'openai';
 import { Embedding } from 'openai/resources';
-import { PineconeEnvironmentVarsNotSupportedError } from '@pinecone-database/pinecone/dist/errors';
 
 dotenv.config();
 
@@ -101,7 +100,7 @@ export class Vectorstore {
         } else {
             // Start processing the document.
             doc.ai_document_status = 'PROGRESS';
-            const local_file_path: string = CsvCast(doc.data)?.url?.pathname ?? PDFCast(doc.data)?.url?.pathname ?? VideoCast(doc.data)?.url?.pathname ?? AudioCast(doc.data)?.url?.pathname;
+            const local_file_path = CsvCast(doc.data)?.url?.pathname ?? PDFCast(doc.data)?.url?.pathname ?? VideoCast(doc.data)?.url?.pathname ?? AudioCast(doc.data)?.url?.pathname;
 
             if (!local_file_path) {
                 console.log('Invalid file path.');
@@ -112,13 +111,13 @@ export class Vectorstore {
             let result: AI_Document & { doc_id: string };
             if (isAudioOrVideo) {
                 console.log('Processing media file...');
-                const response = await Networking.PostToServer('/processMediaFile', { fileName: path.basename(local_file_path) });
+                const response = (await Networking.PostToServer('/processMediaFile', { fileName: path.basename(local_file_path) })) as { [key: string]: unknown };
                 const segmentedTranscript = response.condensed;
                 console.log(segmentedTranscript);
-                const summary = response.summary;
+                const summary = response.summary as string;
                 doc.summary = summary;
                 // Generate embeddings for each chunk
-                const texts = segmentedTranscript.map((chunk: any) => chunk.text);
+                const texts = (segmentedTranscript as { text: string }[])?.map(chunk => chunk.text);
 
                 try {
                     const embeddingsResponse = await this.openai.embeddings.create({
@@ -138,7 +137,7 @@ export class Vectorstore {
                         file_name: local_file_path,
                         num_pages: 0,
                         summary: '',
-                        chunks: segmentedTranscript.map((chunk: any, index: number) => ({
+                        chunks: (segmentedTranscript as { text: string; start: number; end: number; indexes: string[] }[]).map((chunk, index) => ({
                             id: uuidv4(),
                             values: (embeddingsResponse.data as Embedding[])[index].embedding, // Assign embedding
                             metadata: {
@@ -173,7 +172,7 @@ export class Vectorstore {
             } else {
                 // Existing document processing logic remains unchanged
                 console.log('Processing regular document...');
-                const { jobId } = await Networking.PostToServer('/createDocument', { file_path: local_file_path });
+                const { jobId } = (await Networking.PostToServer('/createDocument', { file_path: local_file_path })) as { jobId: string };
 
                 while (true) {
                     await new Promise(resolve => setTimeout(resolve, 2000));
@@ -297,7 +296,7 @@ export class Vectorstore {
                 encoding_format: 'float',
             });
 
-            let queryEmbedding = queryEmbeddingResponse.data[0].embedding;
+            const queryEmbedding = queryEmbeddingResponse.data[0].embedding;
 
             // Extract the embedding from the response.
 
diff --git a/src/client/views/nodes/formattedText/DailyJournal.tsx b/src/client/views/nodes/formattedText/DailyJournal.tsx
index 797a9b812..871c556e6 100644
--- a/src/client/views/nodes/formattedText/DailyJournal.tsx
+++ b/src/client/views/nodes/formattedText/DailyJournal.tsx
@@ -161,7 +161,7 @@ export class DailyJournal extends ViewBoxAnnotatableComponent<FieldViewProps>()
 
                         // Check if doc or selection changed
                         if (!prevState.doc.eq(state.doc) || !prevState.selection.eq(state.selection)) {
-                            let found = false;
+                            const found = false;
                             const textToRemove = this.predictiveText;
 
                             state.doc.descendants((node, pos) => {
diff --git a/src/client/views/nodes/scrapbook/ScrapbookBox.tsx b/src/client/views/nodes/scrapbook/ScrapbookBox.tsx
index b241b27d7..6cfe9a62c 100644
--- a/src/client/views/nodes/scrapbook/ScrapbookBox.tsx
+++ b/src/client/views/nodes/scrapbook/ScrapbookBox.tsx
@@ -1,6 +1,6 @@
 import { action, makeObservable, observable } from 'mobx';
 import * as React from 'react';
-import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc';
+import { Doc, DocListCast } from '../../../../fields/Doc';
 import { List } from '../../../../fields/List';
 import { emptyFunction } from '../../../../Utils';
 import { Docs } from '../../../documents/Documents';
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index 1891cfd4c..e8a5235c9 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -50,7 +50,7 @@ interface IAnnotationProps extends FieldViewProps {
 }
 @observer
 export class Annotation extends ObservableReactComponent<IAnnotationProps> {
-    constructor(props: any) {
+    constructor(props: IAnnotationProps) {
         super(props);
         makeObservable(this);
     }
@@ -58,7 +58,7 @@ export class Annotation extends ObservableReactComponent<IAnnotationProps> {
     @computed get linkHighlighted() {
         const found = LinkManager.Instance.getAllDirectLinks(this._props.annoDoc).find(link => {
             const a1 = Doc.getOppositeAnchor(link, this._props.annoDoc);
-            return a1 && Doc.GetBrushStatus(DocCast(a1.annotationOn, a1));
+            return a1 && Doc.GetBrushStatus(DocCast(a1.annotationOn, a1)!);
         });
         return found;
     }
@@ -112,6 +112,7 @@ export class Annotation extends ObservableReactComponent<IAnnotationProps> {
     outline = () => (this.linkHighlighted ? 'solid 1px lightBlue' : undefined);
     background = () => (this._props.annoDoc[Highlight] ? 'orange' : StrCast(this._props.annoDoc.backgroundColor));
     render() {
+        // eslint-disable-next-line @typescript-eslint/no-unused-vars
         const forceRenderHack = [this.background(), this.outline(), this.opacity()]; // forces a re-render when these change -- because RegionAnnotation doesn't do this internally..
         return (
             <div style={{ display: this._props.annoDoc.textCopied && !Doc.GetBrushHighlightStatus(this._props.annoDoc) ? 'none' : undefined }}>
@@ -121,7 +122,7 @@ export class Annotation extends ObservableReactComponent<IAnnotationProps> {
                     .map(([x, y, width, height]) => (
                         <div
                             key={'' + x + y + width + height}
-                            style={{ pointerEvents: this._props.pointerEvents?.() as any }}
+                            style={{ pointerEvents: this._props.pointerEvents?.() as Property.PointerEvents }}
                             onPointerDown={this.onPointerDown}
                             onContextMenu={this.onContextMenu}
                             onPointerEnter={() => {
diff --git a/src/client/views/search/FaceRecognitionHandler.tsx b/src/client/views/search/FaceRecognitionHandler.tsx
index f60eb486e..cb837e3ab 100644
--- a/src/client/views/search/FaceRecognitionHandler.tsx
+++ b/src/client/views/search/FaceRecognitionHandler.tsx
@@ -162,7 +162,7 @@ export class FaceRecognitionHandler {
         });
         uniqueFaceDoc.$face = `Face${faceDocNum}`;
         uniqueFaceDoc.$face_annos = new List<Doc>();
-        Doc.SetContainer(uniqueFaceDoc, Doc.MyFaceCollection);
+        Doc.MyFaceCollection && Doc.SetContainer(uniqueFaceDoc, Doc.MyFaceCollection);
 
         Doc.ActiveDashboard && Doc.AddDocToList(Doc.ActiveDashboard[DocData], 'myUniqueFaces', uniqueFaceDoc);
         return uniqueFaceDoc;
@@ -208,9 +208,9 @@ export class FaceRecognitionHandler {
             setTimeout(() => this.classifyFacesInImage(imgDoc), 1000);
         } else {
             const imgUrl = ImageCast(imgDoc[Doc.LayoutDataKey(imgDoc)]);
-            if (imgUrl && !DocListCast(Doc.MyFaceCollection.examinedFaceDocs).includes(imgDoc[DocData])) {
+            if (imgUrl && !DocListCast(Doc.MyFaceCollection?.examinedFaceDocs).includes(imgDoc[DocData])) {
                 // only examine Docs that have an image and that haven't already been examined.
-                Doc.AddDocToList(Doc.MyFaceCollection, 'examinedFaceDocs', imgDoc[DocData]);
+                Doc.MyFaceCollection && Doc.AddDocToList(Doc.MyFaceCollection, 'examinedFaceDocs', imgDoc[DocData]);
                 FaceRecognitionHandler.loadImage(imgUrl).then(
                     // load image and analyze faces
                     img => faceapi
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 1c17dd369..ba94f0504 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -231,7 +231,7 @@ export class Doc extends RefField {
     public static AddLink: (link: Doc, checkExists?: boolean) => void;
     public static DeleteLink: (link: Doc) => void;
     public static Links: (link: Doc | undefined) => Doc[];
-    public static getOppositeAnchor: (linkDoc: Doc, anchor: Doc) => Doc | undefined;
+    public static getOppositeAnchor: (linkDoc: Doc | undefined, anchor: Doc | undefined) => Doc | undefined;
     // KeyValueBox SetField (defined there)
     public static SetField: (doc: Doc, key: string, value: string, forceOnDelegate?: boolean, setResult?: (value: FieldResult) => void) => boolean;
     // UserDoc "API"
diff --git a/src/fields/Types.ts b/src/fields/Types.ts
index e6755f828..ba2e9bb6f 100644
--- a/src/fields/Types.ts
+++ b/src/fields/Types.ts
@@ -1,5 +1,6 @@
 import { DateField } from './DateField';
 import { Doc, FieldType, FieldResult, Opt } from './Doc';
+import { InkField } from './InkField';
 import { List } from './List';
 import { ProxyField } from './Proxy';
 import { RefField } from './RefField';
@@ -99,6 +100,7 @@ export function VideoCast (field: FieldResult, defaultVal: VideoField | null = n
 export function AudioCast (field: FieldResult, defaultVal: AudioField | null = null)   { return Cast(field, AudioField, defaultVal); } // prettier-ignore
 export function PDFCast   (field: FieldResult, defaultVal: PdfField | null = null)     { return Cast(field, PdfField, defaultVal); } // prettier-ignore
 export function ImageCast (field: FieldResult, defaultVal: ImageField | null = null)   { return Cast(field, ImageField, defaultVal); } // prettier-ignore
+export function InkCast   (field: FieldResult, defaultVal: InkField | null = null)     { return Cast(field, InkField, defaultVal); } // prettier-ignore
 
 export function ImageCastToNameType(field: FieldResult, defaultVal: ImageField | null = null) {
     const href = ImageCast(field, defaultVal)?.url.href;
-- 
cgit v1.2.3-70-g09d2

