From 146f8622d5bac2edc6b09f57c173bd057dfbcfad Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 8 Jul 2022 00:17:26 -0400
Subject: restructured currentUserUtils to avoid having import cycles.

---
 src/mobile/MobileMain.tsx | 32 ++++++++++++++++++--------------
 1 file changed, 18 insertions(+), 14 deletions(-)

(limited to 'src/mobile/MobileMain.tsx')

diff --git a/src/mobile/MobileMain.tsx b/src/mobile/MobileMain.tsx
index 4a1e26078..f85f05f53 100644
--- a/src/mobile/MobileMain.tsx
+++ b/src/mobile/MobileMain.tsx
@@ -1,25 +1,29 @@
-import { MobileInterface } from "./MobileInterface";
-import { Docs } from "../client/documents/Documents";
-import { CurrentUserUtils } from "../client/util/CurrentUserUtils";
-import * as ReactDOM from 'react-dom';
 import * as React from 'react';
-import { DocServer } from "../client/DocServer";
-import { AssignAllExtensions } from "../extensions/General/Extensions";
+import * as ReactDOM from 'react-dom';
+import { DocServer } from '../client/DocServer';
+import { Docs } from '../client/documents/Documents';
+import { CurrentUserUtils } from '../client/util/CurrentUserUtils';
+import { AssignAllExtensions } from '../extensions/General/Extensions';
+import { MobileInterface } from './MobileInterface';
 
 AssignAllExtensions();
 
 (async () => {
     const info = await CurrentUserUtils.loadCurrentUser();
-    DocServer.init(window.location.protocol, window.location.hostname, 4321, info.email + " (mobile)");
+    DocServer.init(window.location.protocol, window.location.hostname, 4321, info.email + ' (mobile)');
     await Docs.Prototypes.initialize();
-    if (info.id !== "__guest__") {
+    if (info.id !== '__guest__') {
         // a guest will not have an id registered
         await CurrentUserUtils.loadUserDocument(info.id);
     }
-    document.getElementById('root')!.addEventListener('wheel', event => {
-        if (event.ctrlKey) {
-            event.preventDefault();
-        }
-    }, true);
+    document.getElementById('root')!.addEventListener(
+        'wheel',
+        event => {
+            if (event.ctrlKey) {
+                event.preventDefault();
+            }
+        },
+        true
+    );
     ReactDOM.render(<MobileInterface />, document.getElementById('root'));
-})();
\ No newline at end of file
+})();
-- 
cgit v1.2.3-70-g09d2


From 724c1c1228aa601fdbbed93dde68a5a9fa9c5ae9 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 20 Jul 2022 09:12:11 -0400
Subject: added a 'guest' login mode.  added ability for mainView to be any
 doc, not just a docking collection.

---
 src/client/DocServer.ts                            |   4 +-
 src/client/util/CurrentUserUtils.ts                |  18 +-
 src/client/util/GroupManager.tsx                   |   9 +-
 src/client/util/SettingsManager.tsx                |   3 +-
 src/client/util/SharingManager.tsx                 |   3 +-
 src/client/util/TrackMovements.ts                  | 141 ++++++++-------
 src/client/views/ContextMenu.scss                  |  22 +--
 src/client/views/ContextMenuItem.tsx               |  84 +++++----
 src/client/views/DashboardView.tsx                 |   2 +-
 src/client/views/GlobalKeyHandler.ts               |   4 +-
 src/client/views/Main.tsx                          |  51 +++---
 src/client/views/MainView.tsx                      |  53 ++++--
 src/client/views/PropertiesDocContextSelector.tsx  |   2 +-
 .../views/collections/CollectionDockingView.tsx    |  70 ++++----
 src/client/views/collections/CollectionView.tsx    |   2 +-
 src/client/views/collections/TabDocView.tsx        |  15 +-
 src/client/views/nodes/MapBox/MapBox.tsx           |  48 ++---
 src/client/views/nodes/PDFBox.tsx                  |   5 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |   2 +-
 src/client/views/nodes/trails/PresBox.tsx          |   2 +-
 src/client/views/topbar/TopBar.tsx                 |   2 +-
 src/fields/util.ts                                 |  47 +++--
 src/mobile/MobileInkOverlay.tsx                    | 119 ++++++-------
 src/mobile/MobileMain.tsx                          |   5 +-
 src/server/ApiManagers/UserManager.ts              |  77 ++++----
 src/server/RouteManager.ts                         |  58 +++---
 src/server/authentication/AuthenticationManager.ts |  31 ++--
 src/server/authentication/DashUserModel.ts         | 106 ++++++-----
 src/server/index.ts                                | 108 +++++-------
 src/server/websocket.ts                            | 194 +++++++++++----------
 views/login.pug                                    |   4 +-
 31 files changed, 682 insertions(+), 609 deletions(-)

(limited to 'src/mobile/MobileMain.tsx')

diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 1b0ba6bc3..5a34fcf11 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -77,7 +77,7 @@ export namespace DocServer {
     }
 
     export function getFieldWriteMode(field: string) {
-        return fieldWriteModes[field] || WriteMode.Default;
+        return Doc.CurrentUserEmail === 'guest' ? WriteMode.LiveReadonly : fieldWriteModes[field] || WriteMode.Default;
     }
 
     export function registerDocWithCachedUpdate(doc: Doc, field: string, oldValue: any) {
@@ -178,7 +178,7 @@ export namespace DocServer {
                 _isReadOnly = true;
                 _CreateField = field => (_cache[field[Id]] = field);
                 _UpdateField = emptyFunction;
-                _RespondToUpdate = emptyFunction; // bcz: option: don't clear RespondToUpdate to continue to receive updates as others change the DB
+                // _RespondToUpdate = emptyFunction; // bcz: option: don't clear RespondToUpdate to continue to receive updates as others change the DB
             }
         }
 
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 6c80cf0f4..bbf2ff3f9 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -1,6 +1,7 @@
 import { reaction } from "mobx";
 import * as rp from 'request-promise';
 import { Doc, DocListCast, DocListCastAsync, Opt } from "../../fields/Doc";
+import { Id } from "../../fields/FieldSymbols";
 import { List } from "../../fields/List";
 import { PrefetchProxy } from "../../fields/Proxy";
 import { RichTextField } from "../../fields/RichTextField";
@@ -14,6 +15,7 @@ import { DocServer } from "../DocServer";
 import { Docs, DocumentOptions, DocUtils, FInfo } from "../documents/Documents";
 import { CollectionViewType, DocumentType } from "../documents/DocumentTypes";
 import { TreeViewType } from "../views/collections/CollectionTreeView";
+import { DashboardView } from "../views/DashboardView";
 import { Colors } from "../views/global/globalEnums";
 import { MainView } from "../views/MainView";
 import { ButtonType, NumButtonType } from "../views/nodes/button/FontIconBox";
@@ -762,8 +764,6 @@ export class CurrentUserUtils {
 
             doc["clickFuncs-child"] = Docs.Create.TreeDocument([openInTarget, openDetail], { title: "on Child Click function templates", system: true });
         }
-        // this is equivalent to using PrefetchProxies to make sure all the childClickFuncs have been retrieved.
-        PromiseValue(Cast(doc["clickFuncs-child"], Doc)).then(func => func && PromiseValue(func.data).then(DocListCast));
 
         if (doc.clickFuncs === undefined) {
             const onClick = Docs.Create.ScriptingDocument(undefined, {
@@ -789,7 +789,6 @@ export class CurrentUserUtils {
             }, "onCheckedClick");
             doc.clickFuncs = Docs.Create.TreeDocument([onClick, onChildClick, onDoubleClick, onCheckedClick], { title: "onClick funcs", system: true });
         }
-        PromiseValue(Cast(doc.clickFuncs, Doc)).then(func => func && PromiseValue(func.data).then(DocListCast));
 
         return doc.clickFuncs as Doc;
     }
@@ -886,13 +885,20 @@ export class CurrentUserUtils {
     public static async loadUserDocument(id: string) {
         await rp.get(Utils.prepend("/getUserDocumentIds")).then(ids => {
             const { userDocumentId, sharingDocumentId, linkDatabaseId } = JSON.parse(ids);
-            if (userDocumentId !== "guest") {
+            if (userDocumentId) {
                 return DocServer.GetRefField(userDocumentId).then(async field => {
                     Docs.newAccount = !(field instanceof Doc);
                     await Docs.Prototypes.initialize();
                     const userDoc = Docs.newAccount ? new Doc(userDocumentId, true) : field as Doc;
-                    Docs.newAccount &&(userDoc.activePage = "home");
-                    return this.updateUserDocument(Doc.SetUserDoc(userDoc), sharingDocumentId, linkDatabaseId);
+                    this.updateUserDocument(Doc.SetUserDoc(userDoc), sharingDocumentId, linkDatabaseId);
+                    if (Docs.newAccount) {
+                        if (Doc.CurrentUserEmail === "guest") {
+                            DashboardView.createNewDashboard(undefined, "guest dashboard");
+                        } else {
+                            userDoc.activePage = "home";
+                        }
+                    }
+                    return userDoc;
                 });
             } else {
                 throw new Error("There should be a user id! Why does Dash think there isn't one?");
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index 59334f6a2..c8b784390 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -14,6 +14,7 @@ import { GroupMemberView } from './GroupMemberView';
 import { SharingManager, User } from './SharingManager';
 import { listSpec } from '../../fields/Schema';
 import { DateField } from '../../fields/DateField';
+import { Id } from '../../fields/FieldSymbols';
 
 /**
  * Interface for options for the react-select component
@@ -49,9 +50,11 @@ export class GroupManager extends React.Component<{}> {
      * Fetches the list of users stored on the database.
      */
     populateUsers = async () => {
-        const userList = await RequestPromise.get(Utils.prepend('/getUsers'));
-        const raw = JSON.parse(userList) as User[];
-        raw.map(action(user => !this.users.some(umail => umail === user.email) && this.users.push(user.email)));
+        if (Doc.UserDoc()[Id] !== '__guest__') {
+            const userList = await RequestPromise.get(Utils.prepend('/getUsers'));
+            const raw = JSON.parse(userList) as User[];
+            raw.map(action(user => !this.users.some(umail => umail === user.email) && this.users.push(user.email)));
+        }
     };
 
     /**
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index 12d1793af..cf143c5e8 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -4,6 +4,7 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { ColorState, SketchPicker } from 'react-color';
 import { Doc } from '../../fields/Doc';
+import { Id } from '../../fields/FieldSymbols';
 import { BoolCast, Cast, StrCast } from '../../fields/Types';
 import { addStyleSheet, addStyleSheetRule, Utils } from '../../Utils';
 import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
@@ -81,7 +82,7 @@ export class SettingsManager extends React.Component<{}> {
         if (this.playgroundMode) {
             DocServer.Control.makeReadOnly();
             addStyleSheetRule(SettingsManager._settingsStyle, 'topbar-inner-container', { background: 'red !important' });
-        } else DocServer.Control.makeEditable();
+        } else Doc.CurrentUserEmail !== 'guest' && DocServer.Control.makeEditable();
     });
 
     @undoBatch
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 793027ea1..895bd3374 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -23,6 +23,7 @@ import { GroupMemberView } from './GroupMemberView';
 import { SelectionManager } from './SelectionManager';
 import './SharingManager.scss';
 import { LinkManager } from './LinkManager';
+import { Id } from '../../fields/FieldSymbols';
 
 export interface User {
     email: string;
@@ -136,7 +137,7 @@ export class SharingManager extends React.Component<{}> {
      * Populates the list of validated users (this.users) by adding registered users which have a sharingDocument.
      */
     populateUsers = async () => {
-        if (!this.populating) {
+        if (!this.populating && Doc.UserDoc()[Id] !== '__guest__') {
             this.populating = true;
             const userList = await RequestPromise.get(Utils.prepend('/getUsers'));
             const raw = JSON.parse(userList) as User[];
diff --git a/src/client/util/TrackMovements.ts b/src/client/util/TrackMovements.ts
index d512e4802..4a2ccd706 100644
--- a/src/client/util/TrackMovements.ts
+++ b/src/client/util/TrackMovements.ts
@@ -1,27 +1,26 @@
-import { IReactionDisposer, observable, observe, reaction } from "mobx";
-import { NumCast } from "../../fields/Types";
-import { Doc, DocListCast } from "../../fields/Doc";
-import { CollectionDockingView } from "../views/collections/CollectionDockingView";
-import { Id } from "../../fields/FieldSymbols";
+import { IReactionDisposer, observable, observe, reaction } from 'mobx';
+import { NumCast } from '../../fields/Types';
+import { Doc, DocListCast } from '../../fields/Doc';
+import { CollectionDockingView } from '../views/collections/CollectionDockingView';
+import { Id } from '../../fields/FieldSymbols';
 
 export type Movement = {
-    time: number,
-    panX: number,
-    panY: number,
-    scale: number,
-    docId: string,
-}
+    time: number;
+    panX: number;
+    panY: number;
+    scale: number;
+    docId: string;
+};
 
 export type Presentation = {
-    movements: Movement[] | null,
-    totalTime: number,
-    meta: Object | Object[],
-}
+    movements: Movement[] | null;
+    totalTime: number;
+    meta: Object | Object[];
+};
 
 export class TrackMovements {
-
     private static get NULL_PRESENTATION(): Presentation {
-        return { movements: null, meta: {}, totalTime: -1, }
+        return { movements: null, meta: {}, totalTime: -1 };
     }
 
     // instance variables
@@ -32,16 +31,17 @@ export class TrackMovements {
     private recordingFFViews: Map<string, IReactionDisposer> | null;
     private tabChangeDisposeFunc: IReactionDisposer | null;
 
-
     // create static instance and getter for global use
     @observable static _instance: TrackMovements;
-    static get Instance(): TrackMovements { return TrackMovements._instance }
+    static get Instance(): TrackMovements {
+        return TrackMovements._instance;
+    }
     constructor() {
         // init the global instance
         TrackMovements._instance = this;
 
         // init the instance variables
-        this.currentPresentation = TrackMovements.NULL_PRESENTATION
+        this.currentPresentation = TrackMovements.NULL_PRESENTATION;
         this.tracking = false;
         this.absoluteStart = -1;
 
@@ -52,28 +52,37 @@ export class TrackMovements {
 
     // little helper :)
     private get nullPresentation(): boolean {
-        return this.currentPresentation.movements === null
+        return this.currentPresentation.movements === null;
     }
 
     private addRecordingFFView(doc: Doc, key: string = doc[Id]): void {
         // console.info('adding dispose func : docId', key, 'doc', doc);
 
-        if (this.recordingFFViews === null) { console.warn('addFFView on null RecordingApi'); return; }
-        if (this.recordingFFViews.has(key)) { console.warn('addFFView : key already in map'); return; }
+        if (this.recordingFFViews === null) {
+            console.warn('addFFView on null RecordingApi');
+            return;
+        }
+        if (this.recordingFFViews.has(key)) {
+            console.warn('addFFView : key already in map');
+            return;
+        }
 
         const disposeFunc = reaction(
-            () => ({ x: NumCast(doc.panX, -1), y: NumCast(doc.panY, -1), scale: NumCast(doc.viewScale, 0)}),
-            (res) => (res.x !== -1 && res.y !== -1 && this.tracking) && this.trackMovement(res.x, res.y, key, res.scale),
+            () => ({ x: NumCast(doc.panX, -1), y: NumCast(doc.panY, -1), scale: NumCast(doc.viewScale, 0) }),
+            res => res.x !== -1 && res.y !== -1 && this.tracking && this.trackMovement(res.x, res.y, key, res.scale)
         );
         this.recordingFFViews?.set(key, disposeFunc);
     }
 
     private removeRecordingFFView = (key: string) => {
         // console.info('removing dispose func : docId', key);
-        if (this.recordingFFViews === null) { console.warn('removeFFView on null RecordingApi'); return; }
+        if (this.recordingFFViews === null) {
+            console.warn('removeFFView on null RecordingApi');
+            return;
+        }
         this.recordingFFViews.get(key)?.();
         this.recordingFFViews.delete(key);
-    }
+    };
 
     // in the case where only one tab was changed (updates not across dashboards), set only one to true
     private updateRecordingFFViewsFromTabs = (tabbedDocs: Doc[], onlyOne = false) => {
@@ -86,7 +95,6 @@ export class TrackMovements {
             if (isFFView(DashDoc)) tabbedFFViews.add(DashDoc[Id]);
         }
 
-
         // new tab was added - need to add it
         if (tabbedFFViews.size > this.recordingFFViews.size) {
             for (const DashDoc of tabbedDocs) {
@@ -103,13 +111,13 @@ export class TrackMovements {
         // tab was removed - need to remove it from recordingFFViews
         else if (tabbedFFViews.size < this.recordingFFViews.size) {
             for (const [key] of this.recordingFFViews) {
-                if (!tabbedFFViews.has(key)) { 
+                if (!tabbedFFViews.has(key)) {
                     this.removeRecordingFFView(key);
                     if (onlyOne) return;
-                } 
+                }
             }
         }
-    }
+    };
 
     private initTabTracker = () => {
         if (this.recordingFFViews === null) {
@@ -117,18 +125,19 @@ export class TrackMovements {
         }
 
         // init the dispose funcs on the page
-        const docList = DocListCast(CollectionDockingView.Instance.props.Document.data);
+        const docList = DocListCast(CollectionDockingView.Instance?.props.Document.data);
         this.updateRecordingFFViewsFromTabs(docList);
 
         // create a reaction to monitor changes in tabs
-        this.tabChangeDisposeFunc =
-            reaction(() => CollectionDockingView.Instance.props.Document.data,
-                (change) => {
-                    // TODO: consider changing between dashboards
-                    // console.info('change in tabs', change);
-                    this.updateRecordingFFViewsFromTabs(DocListCast(change), true);
-                });
-    }
+        this.tabChangeDisposeFunc = reaction(
+            () => CollectionDockingView.Instance?.props.Document.data,
+            change => {
+                // TODO: consider changing between dashboards
+                // console.info('change in tabs', change);
+                this.updateRecordingFFViewsFromTabs(DocListCast(change), true);
+            }
+        );
+    };
 
     start = (meta?: Object) => {
         this.initTabTracker();
@@ -142,12 +151,12 @@ export class TrackMovements {
         this.absoluteStart = startDate.getTime();
 
         // (2) assign meta content if it exists
-        this.currentPresentation.meta = meta || {}
+        this.currentPresentation.meta = meta || {};
         // (3) assign start date to currentPresenation
-        this.currentPresentation.movements = []
+        this.currentPresentation.movements = [];
         // (4) set tracking true to allow trackMovements
-        this.tracking = true
-    }
+        this.tracking = true;
+    };
 
     /* stops the video and returns the presentatation; if no presentation, returns undefined */
     yieldPresentation(clearData: boolean = true): Presentation | null {
@@ -157,7 +166,7 @@ export class TrackMovements {
         // set the previus recording view to the play view
         // this.playFFView = this.recordingFFView;
 
-        // ensure we add the endTime now that they are done recording    
+        // ensure we add the endTime now that they are done recording
         const cpy = { ...this.currentPresentation, totalTime: new Date().getTime() - this.absoluteStart };
 
         // reset the current presentation
@@ -169,10 +178,10 @@ export class TrackMovements {
 
     finish = (): void => {
         // make is tracking false
-        this.tracking = false
+        this.tracking = false;
         // reset the RecordingApi instance
         this.clear();
-    }
+    };
 
     private clear = (): void => {
         // clear the disposeFunc if we are done (not tracking)
@@ -187,44 +196,46 @@ export class TrackMovements {
         }
 
         // clear presenation data
-        this.currentPresentation = TrackMovements.NULL_PRESENTATION
+        this.currentPresentation = TrackMovements.NULL_PRESENTATION;
         // clear absoluteStart
-        this.absoluteStart = -1
-    }
+        this.absoluteStart = -1;
+    };
 
     removeAllRecordingFFViews = () => {
-        if (this.recordingFFViews === null) { console.warn('removeAllFFViews on null RecordingApi'); return; }
+        if (this.recordingFFViews === null) {
+            console.warn('removeAllFFViews on null RecordingApi');
+            return;
+        }
 
         for (const [id, disposeFunc] of this.recordingFFViews) {
             // console.info('calling dispose func : docId', id);
             disposeFunc();
             this.recordingFFViews.delete(id);
         }
-    }
+    };
 
     private trackMovement = (panX: number, panY: number, docId: string, scale: number = 0) => {
         // ensure we are recording to track
         if (!this.tracking) {
-            console.error('[recordingApi.ts] trackMovements(): tracking is false')
+            console.error('[recordingApi.ts] trackMovements(): tracking is false');
             return;
         }
         // check to see if the presetation is init - if not, we are between segments
         // TODO: make this more clear - tracking should be "live tracking", not always true when the recording api being used (between start and yieldPres)
-        // bacuse tracking should be false inbetween segments high key 
+        // bacuse tracking should be false inbetween segments high key
         if (this.nullPresentation) {
-            console.warn('[recordingApi.ts] trackMovements(): trying to store movemetns between segments')
+            console.warn('[recordingApi.ts] trackMovements(): trying to store movemetns between segments');
             return;
         }
 
         // get the time
-        const time = new Date().getTime() - this.absoluteStart
+        const time = new Date().getTime() - this.absoluteStart;
         // make new movement object
-        const movement: Movement = { time, panX, panY, scale, docId }
+        const movement: Movement = { time, panX, panY, scale, docId };
 
         // add that movement to the current presentation data's movement array
-        this.currentPresentation.movements && this.currentPresentation.movements.push(movement)
-    }
-
+        this.currentPresentation.movements && this.currentPresentation.movements.push(movement);
+    };
 
     // method that concatenates an array of presentatations into one
     public concatPresentations = (presentations: Presentation[]): Presentation => {
@@ -233,13 +244,15 @@ export class TrackMovements {
         let sumTime = 0;
         let combinedMetas: any[] = [];
 
-        presentations.forEach((presentation) => {
+        presentations.forEach(presentation => {
             const { movements, totalTime, meta } = presentation;
 
             // update movements if they had one
             if (movements) {
                 // add the summed time to the movements
-                const addedTimeMovements = movements.map(move => { return { ...move, time: move.time + sumTime } });
+                const addedTimeMovements = movements.map(move => {
+                    return { ...move, time: move.time + sumTime };
+                });
                 // concat the movements already in the combined presentation with these new ones
                 combinedMovements.push(...addedTimeMovements);
             }
@@ -252,6 +265,6 @@ export class TrackMovements {
         });
 
         // return the combined presentation with the updated total summed time
-		return { movements: combinedMovements, totalTime: sumTime, meta: combinedMetas };
-    }
+        return { movements: combinedMovements, totalTime: sumTime, meta: combinedMetas };
+    };
 }
diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss
index ea24dbf6d..1e6a377de 100644
--- a/src/client/views/ContextMenu.scss
+++ b/src/client/views/ContextMenu.scss
@@ -1,10 +1,10 @@
-@import "global/globalCssVariables";
+@import 'global/globalCssVariables';
 
 .contextMenu-cont {
     position: absolute;
     display: flex;
     z-index: 100000;
-    box-shadow: 0px 3px 4px rgba(0,0,0,30%);
+    box-shadow: 0px 3px 4px rgba(0, 0, 0, 30%);
     flex-direction: column;
     background: whitesmoke;
     border-radius: 3px;
@@ -28,9 +28,9 @@
     position: absolute;
     display: flex;
     z-index: 1000;
-    box-shadow: #AAAAAA .2vw .2vw .4vw;
+    box-shadow: #aaaaaa 0.2vw 0.2vw 0.4vw;
     flex-direction: column;
-    border: 1px solid #BBBBBBBB;
+    border: 1px solid #bbbbbbbb;
     border-radius: 15px;
     padding-top: 10px;
     padding-bottom: 10px;
@@ -49,7 +49,7 @@
     -moz-user-select: none;
     -ms-user-select: none;
     user-select: none;
-    transition: all .1s;
+    transition: all 0.1s;
     border-style: none;
     // padding: 10px 0px 10px 0px;
     white-space: nowrap;
@@ -58,7 +58,7 @@
     text-transform: uppercase;
     padding-right: 30px;
 
-    .icon-background {
+    .contextMenu-item-icon-background {
         pointer-events: all;
         background-color: transparent;
         width: 35px;
@@ -78,7 +78,7 @@
     -moz-user-select: none;
     -ms-user-select: none;
     user-select: none;
-    transition: all .1s;
+    transition: all 0.1s;
     border-style: none;
     // padding: 10px 0px 10px 0px;
     white-space: nowrap;
@@ -89,7 +89,7 @@
 }
 
 .contextMenu-item:hover {
-    border-width: .11px;
+    border-width: 0.11px;
     border-style: none;
     border-color: $medium-gray; //  rgb(187, 186, 186);
     border-bottom-style: solid;
@@ -116,8 +116,8 @@
     -moz-user-select: none;
     -ms-user-select: none;
     user-select: none;
-    transition: all .1s;
-    border-width: .11px;
+    transition: all 0.1s;
+    border-width: 0.11px;
     border-style: none;
     border-color: $medium-gray; //  rgb(187, 186, 186);
     // padding: 10px 0px 10px 0px;
@@ -152,4 +152,4 @@
     padding-left: 10px;
     border: solid black 1px;
     border-radius: 5px;
-}
\ No newline at end of file
+}
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index 30073e21f..dc9c2eb6c 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -1,9 +1,9 @@
-import React = require("react");
-import { observable, action, runInAction } from "mobx";
-import { observer } from "mobx-react";
+import React = require('react');
+import { observable, action, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { UndoManager } from "../util/UndoManager";
+import { UndoManager } from '../util/UndoManager';
 
 export interface OriginalMenuProps {
     description: string;
@@ -37,7 +37,7 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
     }
 
     handleEvent = async (e: React.MouseEvent<HTMLDivElement>) => {
-        if ("event" in this.props) {
+        if ('event' in this.props) {
             this.props.closeMenu?.();
             let batch: UndoManager.Batch | undefined;
             if (this.props.undoable !== false) {
@@ -46,7 +46,7 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
             await this.props.event({ x: e.clientX, y: e.clientY });
             batch?.end();
         }
-    }
+    };
 
     currentTimeout?: any;
     static readonly timeout = 300;
@@ -62,8 +62,11 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
         }
         this._overPosY = e.clientY;
         this._overPosX = e.clientX;
-        this.currentTimeout = setTimeout(action(() => this.overItem = true), ContextMenuItem.timeout);
-    }
+        this.currentTimeout = setTimeout(
+            action(() => (this.overItem = true)),
+            ContextMenuItem.timeout
+        );
+    };
 
     onPointerLeave = () => {
         if (this.currentTimeout) {
@@ -73,57 +76,68 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
         if (!this.overItem) {
             return;
         }
-        this.currentTimeout = setTimeout(action(() => this.overItem = false), ContextMenuItem.timeout);
-    }
+        this.currentTimeout = setTimeout(
+            action(() => (this.overItem = false)),
+            ContextMenuItem.timeout
+        );
+    };
 
     render() {
-        if ("event" in this.props) {
+        if ('event' in this.props) {
             return (
-                <div className={"contextMenu-item" + (this.props.selected ? " contextMenu-itemSelected" : "")} onPointerDown={this.handleEvent}>
+                <div className={'contextMenu-item' + (this.props.selected ? ' contextMenu-itemSelected' : '')} onPointerDown={this.handleEvent}>
                     {this.props.icon ? (
-                        <span className="icon-background">
+                        <span className="contextMenu-item-icon-background">
                             <FontAwesomeIcon icon={this.props.icon} size="sm" />
                         </span>
                     ) : null}
-                    <div className="contextMenu-description">
-                        {this.props.description.replace(":", "")}
-                    </div>
+                    <div className="contextMenu-description">{this.props.description.replace(':', '')}</div>
                 </div>
             );
-        } else if ("subitems" in this.props) {
-            const where = !this.overItem ? "" : this._overPosY < window.innerHeight / 3 ? "flex-start" : this._overPosY > window.innerHeight * 2 / 3 ? "flex-end" : "center";
-            const marginTop = !this.overItem ? "" : this._overPosY < window.innerHeight / 3 ? "20px" : this._overPosY > window.innerHeight * 2 / 3 ? "-20px" : "";
+        } else if ('subitems' in this.props) {
+            const where = !this.overItem ? '' : this._overPosY < window.innerHeight / 3 ? 'flex-start' : this._overPosY > (window.innerHeight * 2) / 3 ? 'flex-end' : 'center';
+            const marginTop = !this.overItem ? '' : this._overPosY < window.innerHeight / 3 ? '20px' : this._overPosY > (window.innerHeight * 2) / 3 ? '-20px' : '';
 
             // here
-            const submenu = !this.overItem ? (null) :
-                <div className="contextMenu-subMenu-cont"
+            const submenu = !this.overItem ? null : (
+                <div
+                    className="contextMenu-subMenu-cont"
                     style={{
-                        marginLeft: window.innerHeight - this._overPosX - 50 > 0 ? "90%" : "20%", marginTop
+                        marginLeft: window.innerHeight - this._overPosX - 50 > 0 ? '90%' : '20%',
+                        marginTop,
                     }}>
-                    {this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />)}
-                </div>;
+                    {this._items.map(prop => (
+                        <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />
+                    ))}
+                </div>
+            );
             if (!(this.props as SubmenuProps).noexpand) {
-                return <div className="contextMenu-inlineMenu">
-                    {this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />)}
-                </div>;
+                return (
+                    <div className="contextMenu-inlineMenu">
+                        {this._items.map(prop => (
+                            <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />
+                        ))}
+                    </div>
+                );
             }
             return (
-                <div className={"contextMenu-item" + (this.props.selected ? " contextMenu-itemSelected" : "")}
-                    style={{ alignItems: where, borderTop: this.props.addDivider ? "solid 1px" : undefined }}
-                    onMouseLeave={this.onPointerLeave} onMouseEnter={this.onPointerEnter}>
+                <div
+                    className={'contextMenu-item' + (this.props.selected ? ' contextMenu-itemSelected' : '')}
+                    style={{ alignItems: where, borderTop: this.props.addDivider ? 'solid 1px' : undefined }}
+                    onMouseLeave={this.onPointerLeave}
+                    onMouseEnter={this.onPointerEnter}>
                     {this.props.icon ? (
-                        <span className="icon-background" onMouseEnter={this.onPointerLeave} style={{ alignItems: "center", alignSelf: "center" }}>
+                        <span className="contextMenu-item-icon-background" onMouseEnter={this.onPointerLeave} style={{ alignItems: 'center', alignSelf: 'center' }}>
                             <FontAwesomeIcon icon={this.props.icon} size="sm" />
                         </span>
                     ) : null}
-                    <div className="contextMenu-description" onMouseEnter={this.onPointerEnter}
-                        style={{ alignItems: "center", alignSelf: "center" }} >
+                    <div className="contextMenu-description" onMouseEnter={this.onPointerEnter} style={{ alignItems: 'center', alignSelf: 'center' }}>
                         {this.props.description}
-                        <FontAwesomeIcon icon={"angle-right"} size="lg" style={{ position: "absolute", right: "10px" }} />
+                        <FontAwesomeIcon icon={'angle-right'} size="lg" style={{ position: 'absolute', right: '10px' }} />
                     </div>
                     {submenu}
                 </div>
             );
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx
index 526a32f5a..9ea9128bd 100644
--- a/src/client/views/DashboardView.tsx
+++ b/src/client/views/DashboardView.tsx
@@ -238,7 +238,7 @@ export class DashboardView extends React.Component {
             } else if (doc.readOnly) {
                 DocServer.Control.makeReadOnly();
             } else {
-                DocServer.Control.makeEditable();
+                Doc.CurrentUserEmail !== 'guest' && DocServer.Control.makeEditable();
             }
         }
 
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 73e0c9933..85f579975 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -116,8 +116,8 @@ export class KeyManager {
                 var doDeselect = true;
                 if (SnappingManager.GetIsDragging()) {
                     DragManager.AbortDrag();
-                } else if (CollectionDockingView.Instance.HasFullScreen) {
-                    CollectionDockingView.Instance.CloseFullScreen();
+                } else if (CollectionDockingView.Instance?.HasFullScreen) {
+                    CollectionDockingView.Instance?.CloseFullScreen();
                 } else if (CollectionStackedTimeline.SelectingRegion) {
                     CollectionStackedTimeline.SelectingRegion = undefined;
                     doDeselect = false;
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index e998f1fb9..4cb1183d0 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -5,6 +5,8 @@
 import * as React from 'react';
 import * as ReactDOM from 'react-dom';
 import { AssignAllExtensions } from '../../extensions/General/Extensions';
+import { Utils } from '../../Utils';
+import { DocServer } from '../DocServer';
 import { Docs } from '../documents/Documents';
 import { CurrentUserUtils } from '../util/CurrentUserUtils';
 import { LinkManager } from '../util/LinkManager'; // this must come before importing Docs and CurrentUserUtils
@@ -19,31 +21,28 @@ AssignAllExtensions();
     MainView.Live = window.location.search.includes('live');
     window.location.search.includes('safe') && CollectionView.SetSafeMode(true);
     const info = await CurrentUserUtils.loadCurrentUser();
-    if (info.id !== '__guest__') {
-        // a guest will not have an id registered
-        await CurrentUserUtils.loadUserDocument(info.id);
-    } else {
-        await Docs.Prototypes.initialize();
+    if (info.email === 'guest') DocServer.Control.makeReadOnly();
+    await CurrentUserUtils.loadUserDocument(info.id);
+    setTimeout(() => {
+        document.getElementById('root')!.addEventListener(
+            'wheel',
+            event => {
+                if (event.ctrlKey) {
+                    event.preventDefault();
+                }
+            },
+            true
+        );
+        const startload = (document as any).startLoad;
+        const loading = Date.now() - (startload ? Number(startload) : Date.now() - 3000);
+        console.log('Loading Time = ' + loading);
+        const d = new Date();
+        d.setTime(d.getTime() + 100 * 24 * 60 * 60 * 1000);
+        const expires = 'expires=' + d.toUTCString();
+        document.cookie = `loadtime=${loading};${expires};path=/`;
         new LinkManager();
-    }
-    document.getElementById('root')!.addEventListener(
-        'wheel',
-        event => {
-            if (event.ctrlKey) {
-                event.preventDefault();
-            }
-        },
-        true
-    );
-    const startload = (document as any).startLoad;
-    const loading = Date.now() - (startload ? Number(startload) : Date.now() - 3000);
-    console.log('Loading Time = ' + loading);
-    const d = new Date();
-    d.setTime(d.getTime() + 100 * 24 * 60 * 60 * 1000);
-    const expires = 'expires=' + d.toUTCString();
-    document.cookie = `loadtime=${loading};${expires};path=/`;
-    new LinkManager();
-    new TrackMovements();
-    new ReplayMovements();
-    ReactDOM.render(<MainView />, document.getElementById('root'));
+        new TrackMovements();
+        new ReplayMovements();
+        ReactDOM.render(<MainView />, document.getElementById('root'));
+    }, 0);
 })();
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index b61cd3409..7e032af5e 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -3,7 +3,7 @@ import { faBuffer, faHireAHelper } from '@fortawesome/free-brands-svg-icons';
 import * as far from '@fortawesome/free-regular-svg-icons';
 import * as fa from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, configure, observable, reaction } from 'mobx';
+import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import * as ReactDOM from 'react-dom';
@@ -77,12 +77,15 @@ export class MainView extends React.Component {
     @observable private _panelContent: string = 'none';
     @observable private _sidebarContent: any = Doc.MyLeftSidebarPanel;
     @observable private _leftMenuFlyoutWidth: number = 0;
+    @computed get _hideUI() {
+        return this.mainDoc && this.mainDoc._viewType !== CollectionViewType.Docking;
+    }
 
     @computed private get dashboardTabHeight() {
-        return 27;
+        return this._hideUI ? 0 : 27;
     } //  27 comes form lm.config.defaultConfig.dimensions.headerHeight in goldenlayout.js
     @computed private get topOfDashUI() {
-        return Number(DASHBOARD_SELECTOR_HEIGHT.replace('px', ''));
+        return this._hideUI ? 0 : Number(DASHBOARD_SELECTOR_HEIGHT.replace('px', ''));
     }
     @computed private get topOfHeaderBarDoc() {
         return this.topOfDashUI;
@@ -105,7 +108,12 @@ export class MainView extends React.Component {
     @computed private get colorScheme() {
         return StrCast(Doc.ActiveDashboard?.colorScheme);
     }
+    @observable mainDoc: Opt<Doc>;
     @computed private get mainContainer() {
+        if (window.location.pathname.startsWith('/doc/')) {
+            DocServer.GetRefField(window.location.pathname.substring('/doc/'.length)).then(main => runInAction(() => (this.mainDoc = main as Doc)));
+            return this.mainDoc;
+        }
         return this.userDoc ? Doc.ActiveDashboard : Doc.GuestDashboard;
     }
     @computed private get headerBarDoc() {
@@ -116,10 +124,10 @@ export class MainView extends React.Component {
     }
 
     headerBarDocWidth = () => this.mainDocViewWidth();
-    headerBarDocHeight = () => SettingsManager.headerBarHeight ?? 0;
-    topMenuHeight = () => 35;
+    headerBarDocHeight = () => (this._hideUI ? 0 : SettingsManager.headerBarHeight ?? 0);
+    topMenuHeight = () => (this._hideUI ? 0 : 35);
     topMenuWidth = returnZero; // value is ignored ...
-    leftMenuWidth = () => Number(LEFT_MENU_WIDTH.replace('px', ''));
+    leftMenuWidth = () => (this._hideUI ? 0 : Number(LEFT_MENU_WIDTH.replace('px', '')));
     leftMenuHeight = () => this._dashUIHeight;
     leftMenuFlyoutWidth = () => this._leftMenuFlyoutWidth;
     leftMenuFlyoutHeight = () => this._dashUIHeight;
@@ -212,7 +220,8 @@ export class MainView extends React.Component {
             const pathname = window.location.pathname.substr(1).split('/');
             if (pathname.length > 1 && pathname[0] === 'doc') {
                 Doc.MainDocId = pathname[1];
-                !this.userDoc && DocServer.GetRefField(pathname[1]).then(action(field => field instanceof Doc && (Doc.GuestTarget = field)));
+                //!this.userDoc &&
+                DocServer.GetRefField(pathname[1]).then(action(field => field instanceof Doc && (Doc.GuestTarget = field)));
             }
         }
 
@@ -455,7 +464,11 @@ export class MainView extends React.Component {
         AudioBox.Enabled = true;
         const targets = document.elementsFromPoint(e.x, e.y);
         if (targets.length) {
-            const targClass = targets[0].className.toString();
+            let targClass = targets[0].className.toString();
+            for (let i = 0; i < targets.length - 1; i++) {
+                if (typeof targets[i].className === 'object') targClass = targets[i + 1].className.toString();
+                else break;
+            }
             !targClass.includes('contextMenu') && ContextMenu.Instance.closeMenu();
             !['timeline-menu-desc', 'timeline-menu-item', 'timeline-menu-input'].includes(targClass) && TimelineMenu.Instance.closeMenu();
         }
@@ -517,7 +530,7 @@ export class MainView extends React.Component {
         return () => (this._exploreMode ? ScriptField.MakeScript('CollectionBrowseClick(documentView, clientX, clientY)', { documentView: 'any', clientX: 'number', clientY: 'number' })! : undefined);
     }
     headerBarScreenXf = () => new Transform(-this.leftScreenOffsetOfMainDocView - this.leftMenuFlyoutWidth(), -this.headerBarDocHeight(), 1);
-
+    mainScreenToLocalXf = () => new Transform(-this.leftScreenOffsetOfMainDocView - this.leftMenuFlyoutWidth(), -this.topOfMainDocContent, 1);
     @computed get headerBarDocView() {
         return (
             <div className="mainView-headerBar" style={{ height: this.headerBarDocHeight() }}>
@@ -557,7 +570,7 @@ export class MainView extends React.Component {
     @computed get mainDocView() {
         return (
             <>
-                {this.headerBarDocView}
+                {this._hideUI ? null : this.headerBarDocView}
                 <DocumentView
                     key="main"
                     Document={this.mainContainer!}
@@ -566,11 +579,11 @@ export class MainView extends React.Component {
                     addDocTab={this.addDocTabFunc}
                     pinToPres={emptyFunction}
                     docViewPath={returnEmptyDoclist}
-                    styleProvider={undefined}
+                    styleProvider={this._hideUI ? DefaultStyleProvider : undefined}
                     rootSelected={returnTrue}
                     isContentActive={returnTrue}
                     removeDocument={undefined}
-                    ScreenToLocalTransform={Transform.Identity}
+                    ScreenToLocalTransform={this._hideUI ? this.mainScreenToLocalXf : Transform.Identity}
                     PanelWidth={this.mainDocViewWidth}
                     PanelHeight={this.mainDocViewHeight}
                     focus={DocUtils.DefaultFocus}
@@ -582,7 +595,7 @@ export class MainView extends React.Component {
                     ContainingCollectionView={undefined}
                     ContainingCollectionDoc={undefined}
                     suppressSetHeight={true}
-                    renderDepth={-1}
+                    renderDepth={this._hideUI ? 0 : -1}
                 />
             </>
         );
@@ -750,7 +763,7 @@ export class MainView extends React.Component {
         const width = this.propertiesWidth() + leftMenuFlyoutWidth;
         return (
             <>
-                {this.leftMenuPanel}
+                {this._hideUI ? null : this.leftMenuPanel}
                 <div key="inner" className={`mainView-innerContent${this.colorScheme}`}>
                     {this.flyout}
                     <div className="mainView-libraryHandle" style={{ left: leftMenuFlyoutWidth - 10 /* ~half width of handle */, display: !this._leftMenuFlyoutWidth ? 'none' : undefined }} onPointerDown={this.onFlyoutPointerDown}>
@@ -759,9 +772,11 @@ export class MainView extends React.Component {
                     <div className="mainView-innerContainer" style={{ width: `calc(100% - ${width}px)` }}>
                         {this.dockingContent}
 
-                        <div className="mainView-propertiesDragger" key="props" onPointerDown={this.onPropertiesPointerDown} style={{ right: this.propertiesWidth() - 1 }}>
-                            <FontAwesomeIcon icon={this.propertiesWidth() < 10 ? 'chevron-left' : 'chevron-right'} color={this.colorScheme === ColorScheme.Dark ? Colors.WHITE : Colors.BLACK} size="sm" />
-                        </div>
+                        {this._hideUI ? null : (
+                            <div className="mainView-propertiesDragger" key="props" onPointerDown={this.onPropertiesPointerDown} style={{ right: this.propertiesWidth() - 1 }}>
+                                <FontAwesomeIcon icon={this.propertiesWidth() < 10 ? 'chevron-left' : 'chevron-right'} color={this.colorScheme === ColorScheme.Dark ? Colors.WHITE : Colors.BLACK} size="sm" />
+                            </div>
+                        )}
                         <div className="properties-container" style={{ width: this.propertiesWidth() }}>
                             {this.propertiesWidth() < 10 ? null : <PropertiesView styleProvider={DefaultStyleProvider} addDocTab={this.addDocTabFunc} width={this.propertiesWidth()} height={this.propertiesHeight()} />}
                         </div>
@@ -962,7 +977,7 @@ export class MainView extends React.Component {
                 <GoogleAuthenticationManager />
                 <DocumentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfHeaderBarDoc} PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} />
                 <ComponentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfMainDocContent} />
-                <TopBar />
+                {this._hideUI ? null : <TopBar />}
                 {LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
                 {DocumentLinksButton.LinkEditorDocView ? <LinkMenu clearLinkEditor={action(() => (DocumentLinksButton.LinkEditorDocView = undefined))} docView={DocumentLinksButton.LinkEditorDocView} /> : null}
                 {LinkDocPreview.LinkInfo ? <LinkDocPreview {...LinkDocPreview.LinkInfo} /> : null}
@@ -973,7 +988,7 @@ export class MainView extends React.Component {
                         default:
                             return (
                                 <>
-                                    <div style={{ position: 'relative', display: LightboxView.LightboxDoc ? 'none' : undefined, zIndex: 1999 }}>
+                                    <div style={{ position: 'relative', display: this._hideUI || LightboxView.LightboxDoc ? 'none' : undefined, zIndex: 1999 }}>
                                         <CollectionMenu panelWidth={this.topMenuWidth} panelHeight={this.topMenuHeight} />
                                     </div>
                                     {this.mainDashboardArea}
diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx
index 0f63ebc1d..9d89ee036 100644
--- a/src/client/views/PropertiesDocContextSelector.tsx
+++ b/src/client/views/PropertiesDocContextSelector.tsx
@@ -40,7 +40,7 @@ export class PropertiesDocContextSelector extends React.Component<PropertiesDocC
                 .keys()
         );
         return doclayouts
-            .filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document))
+            .filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance?.props.Document))
             .filter(doc => !Doc.IsSystem(doc))
             .map(doc => ({ col: doc, target }));
     }
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 42f9bb981..ed9054107 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -29,7 +29,7 @@ const _global = (window /* browser */ || global) /* node */ as any;
 
 @observer
 export class CollectionDockingView extends CollectionSubView() {
-    @observable public static Instance: CollectionDockingView;
+    @observable public static Instance: CollectionDockingView | undefined;
     public static makeDocumentConfig(document: Doc, panelName?: string, width?: number) {
         return {
             type: 'react-component',
@@ -103,12 +103,14 @@ export class CollectionDockingView extends CollectionSubView() {
 
     @undoBatch
     public static CloseSplit(document: Opt<Doc>, panelName?: string): boolean {
-        const tab = Array.from(CollectionDockingView.Instance.tabMap.keys()).find(tab => (panelName ? tab.contentItem.config.props.panelName === panelName : tab.DashDoc === document));
-        if (tab) {
-            const j = tab.header.parent.contentItems.indexOf(tab.contentItem);
-            if (j !== -1) {
-                tab.header.parent.contentItems[j].remove();
-                return CollectionDockingView.Instance.layoutChanged();
+        if (CollectionDockingView.Instance) {
+            const tab = Array.from(CollectionDockingView.Instance.tabMap.keys()).find(tab => (panelName ? tab.contentItem.config.props.panelName === panelName : tab.DashDoc === document));
+            if (tab) {
+                const j = tab.header.parent.contentItems.indexOf(tab.contentItem);
+                if (j !== -1) {
+                    tab.header.parent.contentItems[j].remove();
+                    return CollectionDockingView.Instance.layoutChanged();
+                }
             }
         }
 
@@ -119,19 +121,21 @@ export class CollectionDockingView extends CollectionSubView() {
     public static OpenFullScreen(doc: Doc) {
         SelectionManager.DeselectAll();
         const instance = CollectionDockingView.Instance;
-        if (doc._viewType === CollectionViewType.Docking && doc.layoutKey === 'layout') {
-            return DashboardView.openDashboard(doc);
+        if (instance) {
+            if (doc._viewType === CollectionViewType.Docking && doc.layoutKey === 'layout') {
+                return DashboardView.openDashboard(doc);
+            }
+            const newItemStackConfig = {
+                type: 'stack',
+                content: [CollectionDockingView.makeDocumentConfig(Doc.MakeAlias(doc))],
+            };
+            const docconfig = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout);
+            instance._goldenLayout.root.contentItems[0].addChild(docconfig);
+            docconfig.callDownwards('_$init');
+            instance._goldenLayout._$maximiseItem(docconfig);
+            instance._goldenLayout.emit('stateChanged');
+            instance.stateChanged();
         }
-        const newItemStackConfig = {
-            type: 'stack',
-            content: [CollectionDockingView.makeDocumentConfig(Doc.MakeAlias(doc))],
-        };
-        const docconfig = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout);
-        instance._goldenLayout.root.contentItems[0].addChild(docconfig);
-        docconfig.callDownwards('_$init');
-        instance._goldenLayout._$maximiseItem(docconfig);
-        instance._goldenLayout.emit('stateChanged');
-        instance.stateChanged();
         return true;
     }
 
@@ -146,21 +150,23 @@ export class CollectionDockingView extends CollectionSubView() {
             const newContentItem = stack.layoutManager.createContentItem(newConfig, instance._goldenLayout);
             stack.addChild(newContentItem.contentItems[0], undefined);
             stack.contentItems[activeContentItemIndex].remove();
-            return CollectionDockingView.Instance.layoutChanged();
+            return instance.layoutChanged();
         }
-        const tab = Array.from(CollectionDockingView.Instance.tabMap.keys()).find(tab => tab.contentItem.config.props.panelName === panelName);
+        const tab = Array.from(instance.tabMap.keys()).find(tab => tab.contentItem.config.props.panelName === panelName);
         if (tab) {
             tab.header.parent.addChild(newConfig, undefined);
             const j = tab.header.parent.contentItems.indexOf(tab.contentItem);
             !addToSplit && j !== -1 && tab.header.parent.contentItems[j].remove();
-            return CollectionDockingView.Instance.layoutChanged();
+            return instance.layoutChanged();
         }
         return CollectionDockingView.AddSplit(document, panelName, stack, panelName);
     }
 
     @undoBatch
     public static ToggleSplit(doc: Doc, location: string, stack?: any, panelName?: string) {
-        return Array.from(CollectionDockingView.Instance.tabMap.keys()).findIndex(tab => tab.DashDoc === doc) !== -1 ? CollectionDockingView.CloseSplit(doc) : CollectionDockingView.AddSplit(doc, location, stack, panelName);
+        return CollectionDockingView.Instance && Array.from(CollectionDockingView.Instance.tabMap.keys()).findIndex(tab => tab.DashDoc === doc) !== -1
+            ? CollectionDockingView.CloseSplit(doc)
+            : CollectionDockingView.AddSplit(doc, location, stack, panelName);
     }
 
     //
@@ -170,7 +176,7 @@ export class CollectionDockingView extends CollectionSubView() {
     @action
     public static AddSplit(document: Doc, pullSide: string, stack?: any, panelName?: string) {
         if (document._viewType === CollectionViewType.Docking) return DashboardView.openDashboard(document);
-
+        if (!CollectionDockingView.Instance) return false;
         const tab = Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === document);
         if (tab) {
             tab.header.parent.setActiveContentItem(tab.contentItem);
@@ -453,13 +459,15 @@ export class CollectionDockingView extends CollectionSubView() {
             Doc.AddDocToList(Doc.MyHeaderBar, 'data', tab.DashDoc);
             Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', tab.DashDoc, undefined, true, true);
         }
-        const dview = CollectionDockingView.Instance.props.Document;
-        const fieldKey = CollectionDockingView.Instance.props.fieldKey;
-        Doc.RemoveDocFromList(dview, fieldKey, tab.DashDoc);
-        this.tabMap.delete(tab);
-        tab._disposers && Object.values(tab._disposers).forEach((disposer: any) => disposer?.());
-        tab.reactComponents?.forEach((ele: any) => ReactDOM.unmountComponentAtNode(ele));
-        this.stateChanged();
+        if (CollectionDockingView.Instance) {
+            const dview = CollectionDockingView.Instance.props.Document;
+            const fieldKey = CollectionDockingView.Instance.props.fieldKey;
+            Doc.RemoveDocFromList(dview, fieldKey, tab.DashDoc);
+            this.tabMap.delete(tab);
+            tab._disposers && Object.values(tab._disposers).forEach((disposer: any) => disposer?.());
+            tab.reactComponents?.forEach((ele: any) => ReactDOM.unmountComponentAtNode(ele));
+            this.stateChanged();
+        }
     };
     tabCreated = (tab: any) => {
         this.tabMap.add(tab);
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 2ab5f6247..f0cb23eab 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -225,7 +225,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
                         },
                     })
                 );
-                DocListCast(Cast(Doc.UserDoc()['clickFuncs-child'], Doc, null).data).forEach(childClick =>
+                DocListCast(Cast(Doc.UserDoc()['clickFuncs-child'], Doc, null)?.data).forEach(childClick =>
                     onClicks.push({
                         description: `Set child ${childClick.title}`,
                         icon: 'edit',
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index b8aaea622..2d08b1c09 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -302,6 +302,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                 pinDoc && PresBox.Instance?._selectedArray.set(pinDoc, undefined); //Update selected array
             });
         if (
+            CollectionDockingView.Instance &&
             !Array.from(CollectionDockingView.Instance.tabMap)
                 .map(d => d.DashDoc)
                 .includes(curPres)
@@ -420,7 +421,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
     ScreenToLocalTransform = () => {
         this._forceInvalidateScreenToLocal;
         const { translateX, translateY } = Utils.GetScreenTransform(this._mainCont?.children?.[0] as HTMLElement);
-        return CollectionDockingView.Instance?.props.ScreenToLocalTransform().translate(-translateX, -translateY);
+        return CollectionDockingView.Instance?.props.ScreenToLocalTransform().translate(-translateX, -translateY) ?? Transform.Identity();
     };
     PanelWidth = () => this._panelWidth;
     PanelHeight = () => this._panelHeight;
@@ -449,9 +450,9 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                     PanelWidth={this.PanelWidth}
                     PanelHeight={this.PanelHeight}
                     styleProvider={DefaultStyleProvider}
-                    docFilters={CollectionDockingView.Instance.childDocFilters}
-                    docRangeFilters={CollectionDockingView.Instance.childDocRangeFilters}
-                    searchFilterDocs={CollectionDockingView.Instance.searchFilterDocs}
+                    docFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDoclist}
+                    docRangeFilters={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDoclist}
+                    searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist}
                     addDocument={undefined}
                     removeDocument={this.remDocTab}
                     addDocTab={this.addDocTab}
@@ -624,9 +625,9 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
                     styleProvider={TabMinimapView.miniStyleProvider}
                     addDocTab={this.props.addDocTab}
                     pinToPres={TabDocView.PinDoc}
-                    docFilters={CollectionDockingView.Instance.childDocFilters}
-                    docRangeFilters={CollectionDockingView.Instance.childDocRangeFilters}
-                    searchFilterDocs={CollectionDockingView.Instance.searchFilterDocs}
+                    docFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDoclist}
+                    docRangeFilters={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDoclist}
+                    searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist}
                     fitContentsToBox={returnTrue}
                 />
                 <div className="miniOverlay" onPointerDown={this.miniDown}>
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index bf4c029b2..c4b42301e 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -1,6 +1,6 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Autocomplete, GoogleMap, GoogleMapProps, Marker } from '@react-google-maps/api';
-import { action, computed, IReactionDisposer, observable, ObservableMap } from 'mobx';
+import { action, computed, IReactionDisposer, observable, ObservableMap, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast, Opt, WidthSym } from '../../../../fields/Doc';
@@ -12,6 +12,7 @@ import { emptyFunction, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, Uti
 import { Docs } from '../../../documents/Documents';
 import { DragManager } from '../../../util/DragManager';
 import { SnappingManager } from '../../../util/SnappingManager';
+import { UndoManager } from '../../../util/UndoManager';
 import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent';
 import { Colors } from '../../global/globalEnums';
@@ -368,23 +369,27 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         setupMoveUpEvents(
             this,
             e,
-            (e, down, delta) => {
-                const localDelta = this.props
-                    .ScreenToLocalTransform()
-                    .scale(this.props.scaling?.() || 1)
-                    .transformDirection(delta[0], delta[1]);
-                const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '-nativeWidth']);
-                const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
-                const ratio = (curNativeWidth + localDelta[0] / (this.props.scaling?.() || 1)) / nativeWidth;
-                if (ratio >= 1) {
-                    this.layoutDoc.nativeWidth = nativeWidth * ratio;
-                    this.layoutDoc._width = this.layoutDoc[WidthSym]() + localDelta[0];
-                    this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth;
-                }
-                return false;
-            },
+            (e, down, delta) =>
+                runInAction(() => {
+                    const localDelta = this.props
+                        .ScreenToLocalTransform()
+                        .scale(this.props.scaling?.() || 1)
+                        .transformDirection(delta[0], delta[1]);
+                    const fullWidth = this.layoutDoc[WidthSym]();
+                    const mapWidth = fullWidth - this.sidebarWidth();
+                    if (this.sidebarWidth() + localDelta[0] > 0) {
+                        this._showSidebar = true;
+                        this.layoutDoc._width = fullWidth + localDelta[0];
+                        this.layoutDoc._sidebarWidthPercent = ((100 * (this.sidebarWidth() + localDelta[0])) / (fullWidth + localDelta[0])).toString() + '%';
+                    } else {
+                        this._showSidebar = false;
+                        this.layoutDoc._width = mapWidth;
+                        this.layoutDoc._sidebarWidthPercent = '0%';
+                    }
+                    return false;
+                }),
             emptyFunction,
-            this.toggleSidebar
+            () => UndoManager.RunInBatch(this.toggleSidebar, 'toggle sidebar map')
         );
     };
 
@@ -470,13 +475,14 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     // TODO: Adding highlight box layer to Maps
     @action
     toggleSidebar = () => {
+        //1.2 * w * ? = .2 * w      .2/1.2
         const prevWidth = this.sidebarWidth();
-        this.layoutDoc._showSidebar = (this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, '0%') === '0%' ? '50%' : '0%') !== '0%';
-        this.layoutDoc._width = this.layoutDoc._showSidebar ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth);
+        this.layoutDoc._showSidebar = (this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, '0%') === '0%' ? `${(100 * 0.2) / 1.2}%` : '0%') !== '0%';
+        this.layoutDoc._width = this.layoutDoc._showSidebar ? NumCast(this.layoutDoc._width) * 1.2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth);
     };
 
     sidebarDown = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), false);
+        setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), true);
     };
     sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => {
         const bounds = this._ref.current!.getBoundingClientRect();
@@ -605,7 +611,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     {this.annotationLayer}
                     <GoogleMap mapContainerStyle={mapContainerStyle} onZoomChanged={this.zoomChanged} onCenterChanged={this.centered} onLoad={this.loadHandler} options={mapOptions}>
                         <Autocomplete onLoad={this.setSearchBox} onPlaceChanged={this.handlePlaceChanged}>
-                            <input className="mapBox-input" ref={this.inputRef} type="text" placeholder="Enter location" />
+                            <input className="mapBox-input" ref={this.inputRef} type="text" onKeyDown={e => e.stopPropagation()} placeholder="Enter location" />
                         </Autocomplete>
 
                         {this.renderMarkers()}
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 5b9d90780..f41f6a1ad 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -13,20 +13,19 @@ import { Docs, DocUtils } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { KeyCodes } from '../../util/KeyCodes';
 import { undoBatch, UndoManager } from '../../util/UndoManager';
+import { CollectionFreeFormView } from '../collections/collectionFreeForm';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
 import { Colors } from '../global/globalEnums';
 import { CreateImage } from '../nodes/WebBoxRenderer';
-import { AnchorMenu } from '../pdf/AnchorMenu';
 import { PDFViewer } from '../pdf/PDFViewer';
 import { SidebarAnnos } from '../SidebarAnnos';
 import { FieldView, FieldViewProps } from './FieldView';
 import { ImageBox } from './ImageBox';
-import './PDFBox.scss';
 import { VideoBox } from './VideoBox';
+import './PDFBox.scss';
 import React = require('react');
-import { CollectionFreeFormView } from '../collections/collectionFreeForm';
 
 @observer
 export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index cfaa428f9..e945920da 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -640,7 +640,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         this.layoutDoc._width = !preview && this.SidebarShown ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth);
     };
     sidebarDown = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), false);
+        setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), true);
     };
     sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => {
         const bounds = this._ref.current!.getBoundingClientRect();
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 292c187e4..6e5eb3300 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -399,7 +399,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const presCollection = Cast(this.layoutDoc.presCollection, Doc, null);
         const collectionDocView = presCollection ? DocumentManager.Instance.getDocumentView(presCollection) : undefined;
         const includesDoc: boolean = DocListCast(presCollection?.data).includes(targetDoc);
-        const tab = Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === srcContext);
+        const tab = CollectionDockingView.Instance && Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === srcContext);
         this.turnOffEdit();
         // Handles the setting of presCollection
         if (includesDoc) {
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index 0bf1575fb..3fc0a237e 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -26,7 +26,7 @@ import './TopBar.scss';
 @observer
 export class TopBar extends React.Component {
     navigateToHome = () => {
-        CollectionDockingView.Instance.CaptureThumbnail()?.then(() => {
+        CollectionDockingView.Instance?.CaptureThumbnail()?.then(() => {
             Doc.ActivePage = 'home';
             DashboardView.closeActiveDashboard(); // bcz: if we do this, we need some other way to keep track, for user convenience, of the last dashboard in use
         });
diff --git a/src/fields/util.ts b/src/fields/util.ts
index cbb560114..41e723119 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -1,41 +1,40 @@
+import { action, observable, runInAction, trace } from 'mobx';
+import { computedFn } from 'mobx-utils';
+import { DocServer } from '../client/DocServer';
+import { SerializationHelper } from '../client/util/SerializationHelper';
 import { UndoManager } from '../client/util/UndoManager';
+import { returnZero } from '../Utils';
+import CursorField from './CursorField';
 import {
-    Doc,
-    FieldResult,
-    UpdatingFromServer,
-    LayoutSym,
-    AclPrivate,
+    AclAdmin,
+    AclAugment,
     AclEdit,
+    AclPrivate,
     AclReadonly,
-    AclAugment,
+    AclSelfEdit,
     AclSym,
+    AclUnset,
     DataSym,
+    Doc,
     DocListCast,
-    AclAdmin,
-    HeightSym,
-    WidthSym,
-    updateCachedAcls,
-    AclUnset,
     DocListCastAsync,
+    FieldResult,
     ForceServerWrite,
+    HeightSym,
     Initializing,
-    AclSelfEdit,
+    LayoutSym,
+    updateCachedAcls,
+    UpdatingFromServer,
+    WidthSym,
 } from './Doc';
-import { SerializationHelper } from '../client/util/SerializationHelper';
-import { ProxyField, PrefetchProxy } from './Proxy';
-import { RefField } from './RefField';
+import { Id, OnUpdate, Parent, Self, SelfProxy, Update } from './FieldSymbols';
+import { List } from './List';
 import { ObjectField } from './ObjectField';
-import { action, observable, runInAction, trace } from 'mobx';
-import { Parent, OnUpdate, Update, Id, SelfProxy, Self } from './FieldSymbols';
-import { DocServer } from '../client/DocServer';
+import { PrefetchProxy, ProxyField } from './Proxy';
+import { RefField } from './RefField';
+import { RichTextField } from './RichTextField';
 import { ComputedField } from './ScriptField';
 import { ScriptCast, StrCast } from './Types';
-import { returnZero } from '../Utils';
-import CursorField from './CursorField';
-import { List } from './List';
-import { SnappingManager } from '../client/util/SnappingManager';
-import { computedFn } from 'mobx-utils';
-import { RichTextField } from './RichTextField';
 
 function _readOnlySetter(): never {
     throw new Error("Documents can't be modified in read-only mode");
diff --git a/src/mobile/MobileInkOverlay.tsx b/src/mobile/MobileInkOverlay.tsx
index d668d134e..6415099fd 100644
--- a/src/mobile/MobileInkOverlay.tsx
+++ b/src/mobile/MobileInkOverlay.tsx
@@ -1,13 +1,12 @@
 import React = require('react');
-import { observer } from "mobx-react";
-import { MobileInkOverlayContent, GestureContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent } from "../server/Message";
-import { observable, action } from "mobx";
-import { GestureUtils } from "../pen-gestures/GestureUtils";
-import "./MobileInkOverlay.scss";
-import { DragManager } from "../client/util/DragManager";
+import { action, observable } from 'mobx';
+import { observer } from 'mobx-react';
 import { DocServer } from '../client/DocServer';
+import { DragManager } from '../client/util/DragManager';
 import { Doc } from '../fields/Doc';
-
+import { GestureUtils } from '../pen-gestures/GestureUtils';
+import { GestureContent, MobileDocumentUploadContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent } from '../server/Message';
+import './MobileInkOverlay.scss';
 
 @observer
 export default class MobileInkOverlay extends React.Component {
@@ -18,7 +17,7 @@ export default class MobileInkOverlay extends React.Component {
     @observable private _height: number = 0;
     @observable private _x: number = -300;
     @observable private _y: number = -300;
-    @observable private _text: string = "";
+    @observable private _text: string = '';
 
     @observable private _offsetX: number = 0;
     @observable private _offsetY: number = 0;
@@ -49,7 +48,7 @@ export default class MobileInkOverlay extends React.Component {
         this._scale = scaledSize.scale;
         this._x = 300; // TODO: center on screen
         this._y = 25; // TODO: center on screen
-        this._text = text ? text : "";
+        this._text = text ? text : '';
     }
 
     @action
@@ -64,31 +63,29 @@ export default class MobileInkOverlay extends React.Component {
         // TODO: figure out why strokes drawn in corner of mobile interface dont get inserted
 
         const { points, bounds } = content;
-        console.log("received points", points, bounds);
+        console.log('received points', points, bounds);
 
         const B = {
-            right: (bounds.right * this._scale) + this._x,
-            left: (bounds.left * this._scale) + this._x, // TODO: scale
-            bottom: (bounds.bottom * this._scale) + this._y,
-            top: (bounds.top * this._scale) + this._y, // TODO: scale
+            right: bounds.right * this._scale + this._x,
+            left: bounds.left * this._scale + this._x, // TODO: scale
+            bottom: bounds.bottom * this._scale + this._y,
+            top: bounds.top * this._scale + this._y, // TODO: scale
             width: bounds.width * this._scale,
             height: bounds.height * this._scale,
         };
 
         const target = document.elementFromPoint(this._x + 10, this._y + 10);
         target?.dispatchEvent(
-            new CustomEvent<GestureUtils.GestureEvent>("dashOnGesture",
-                {
-                    bubbles: true,
-                    detail: {
-                        points: points,
-                        gesture: GestureUtils.Gestures.Stroke,
-                        bounds: B
-                    }
-                }
-            )
+            new CustomEvent<GestureUtils.GestureEvent>('dashOnGesture', {
+                bubbles: true,
+                detail: {
+                    points: points,
+                    gesture: GestureUtils.Gestures.Stroke,
+                    bounds: B,
+                },
+            })
         );
-    }
+    };
 
     uploadDocument = async (content: MobileDocumentUploadContent) => {
         const { docId } = content;
@@ -100,36 +97,34 @@ export default class MobileInkOverlay extends React.Component {
             const complete = new DragManager.DragCompleteEvent(false, dragData);
 
             if (target) {
-                console.log("dispatching upload doc!!!!", target, doc);
+                console.log('dispatching upload doc!!!!', target, doc);
                 target.dispatchEvent(
-                    new CustomEvent<DragManager.DropEvent>("dashOnDrop",
-                        {
-                            bubbles: true,
-                            detail: {
-                                x: this._x,
-                                y: this._y,
-                                complete: complete,
-                                altKey: false,
-                                metaKey: false,
-                                ctrlKey: false,
-                                shiftKey: false,
-                                embedKey: false
-                            }
-                        }
-                    )
+                    new CustomEvent<DragManager.DropEvent>('dashOnDrop', {
+                        bubbles: true,
+                        detail: {
+                            x: this._x,
+                            y: this._y,
+                            complete: complete,
+                            altKey: false,
+                            metaKey: false,
+                            ctrlKey: false,
+                            shiftKey: false,
+                            embedKey: false,
+                        },
+                    })
                 );
             } else {
-                alert("TARGET IS UNDEFINED");
+                alert('TARGET IS UNDEFINED');
             }
         }
-    }
+    };
 
     @action
     dragStart = (e: React.PointerEvent) => {
-        document.removeEventListener("pointermove", this.dragging);
-        document.removeEventListener("pointerup", this.dragEnd);
-        document.addEventListener("pointermove", this.dragging);
-        document.addEventListener("pointerup", this.dragEnd);
+        document.removeEventListener('pointermove', this.dragging);
+        document.removeEventListener('pointerup', this.dragEnd);
+        document.addEventListener('pointermove', this.dragging);
+        document.addEventListener('pointerup', this.dragEnd);
 
         this._isDragging = true;
         this._offsetX = e.pageX - this._mainCont.current!.getBoundingClientRect().left;
@@ -137,7 +132,7 @@ export default class MobileInkOverlay extends React.Component {
 
         e.preventDefault();
         e.stopPropagation();
-    }
+    };
 
     @action
     dragging = (e: PointerEvent) => {
@@ -150,41 +145,39 @@ export default class MobileInkOverlay extends React.Component {
 
         e.preventDefault();
         e.stopPropagation();
-    }
+    };
 
     @action
     dragEnd = (e: PointerEvent) => {
-        document.removeEventListener("pointermove", this.dragging);
-        document.removeEventListener("pointerup", this.dragEnd);
+        document.removeEventListener('pointermove', this.dragging);
+        document.removeEventListener('pointerup', this.dragEnd);
 
         this._isDragging = false;
 
         e.preventDefault();
         e.stopPropagation();
-    }
+    };
 
     render() {
-
         return (
-            <div className="mobileInkOverlay"
+            <div
+                className="mobileInkOverlay"
                 style={{
                     width: this._width,
                     height: this._height,
-                    position: "absolute",
+                    position: 'absolute',
                     transform: `translate(${this._x}px, ${this._y}px)`,
                     zIndex: 30000,
-                    pointerEvents: "none",
-                    borderStyle: this._isDragging ? "solid" : "dashed",
-                }
-                }
-                ref={this._mainCont}
-            >
+                    pointerEvents: 'none',
+                    borderStyle: this._isDragging ? 'solid' : 'dashed',
+                }}
+                ref={this._mainCont}>
                 <p>{this._text}</p>
                 <div className="mobileInkOverlay-border top" onPointerDown={this.dragStart}></div>
                 <div className="mobileInkOverlay-border bottom" onPointerDown={this.dragStart}></div>
                 <div className="mobileInkOverlay-border left" onPointerDown={this.dragStart}></div>
                 <div className="mobileInkOverlay-border right" onPointerDown={this.dragStart}></div>
-            </div >
+            </div>
         );
     }
-}
\ No newline at end of file
+}
diff --git a/src/mobile/MobileMain.tsx b/src/mobile/MobileMain.tsx
index f85f05f53..6cbf86f77 100644
--- a/src/mobile/MobileMain.tsx
+++ b/src/mobile/MobileMain.tsx
@@ -12,10 +12,7 @@ AssignAllExtensions();
     const info = await CurrentUserUtils.loadCurrentUser();
     DocServer.init(window.location.protocol, window.location.hostname, 4321, info.email + ' (mobile)');
     await Docs.Prototypes.initialize();
-    if (info.id !== '__guest__') {
-        // a guest will not have an id registered
-        await CurrentUserUtils.loadUserDocument(info.id);
-    }
+    await CurrentUserUtils.loadUserDocument(info.id);
     document.getElementById('root')!.addEventListener(
         'wheel',
         event => {
diff --git a/src/server/ApiManagers/UserManager.ts b/src/server/ApiManagers/UserManager.ts
index 7be8a1e9f..53e55c1c3 100644
--- a/src/server/ApiManagers/UserManager.ts
+++ b/src/server/ApiManagers/UserManager.ts
@@ -1,10 +1,10 @@
-import ApiManager, { Registration } from "./ApiManager";
-import { Method } from "../RouteManager";
-import { Database } from "../database";
-import { msToTime } from "../ActionUtilities";
-import * as bcrypt from "bcrypt-nodejs";
-import { Opt } from "../../fields/Doc";
-import { WebSocket } from "../websocket";
+import ApiManager, { Registration } from './ApiManager';
+import { Method } from '../RouteManager';
+import { Database } from '../database';
+import { msToTime } from '../ActionUtilities';
+import * as bcrypt from 'bcrypt-nodejs';
+import { Opt } from '../../fields/Doc';
+import { WebSocket } from '../websocket';
 
 export const timeMap: { [id: string]: number } = {};
 interface ActivityUnit {
@@ -13,28 +13,26 @@ interface ActivityUnit {
 }
 
 export default class UserManager extends ApiManager {
-
     protected initialize(register: Registration): void {
-
         register({
             method: Method.GET,
-            subscription: "/getUsers",
+            subscription: '/getUsers',
             secureHandler: async ({ res }) => {
-                const cursor = await Database.Instance.query({}, { email: 1, linkDatabaseId: 1, sharingDocumentId: 1 }, "users");
+                const cursor = await Database.Instance.query({}, { email: 1, linkDatabaseId: 1, sharingDocumentId: 1 }, 'users');
                 const results = await cursor.toArray();
                 res.send(results.map((user: any) => ({ email: user.email, linkDatabaseId: user.linkDatabaseId, sharingDocumentId: user.sharingDocumentId })));
-            }
+            },
         });
 
         register({
             method: Method.POST,
-            subscription: "/setCacheDocumentIds",
+            subscription: '/setCacheDocumentIds',
             secureHandler: async ({ user, req, res }) => {
                 const result: any = {};
                 user.cacheDocumentIds = req.body.cacheDocumentIds;
                 user.save(err => {
                     if (err) {
-                        result.error = [{ msg: "Error while caching documents" }];
+                        result.error = [{ msg: 'Error while caching documents' }];
                     }
                 });
 
@@ -42,32 +40,35 @@ export default class UserManager extends ApiManager {
                 //     console.log(e);
                 // });
                 res.send(result);
-            }
+            },
         });
 
         register({
             method: Method.GET,
-            subscription: "/getUserDocumentIds",
-            secureHandler: ({ res, user }) => res.send({ userDocumentId: user.userDocumentId, linkDatabaseId: user.linkDatabaseId, sharingDocumentId: user.sharingDocumentId })
+            subscription: '/getUserDocumentIds',
+            secureHandler: ({ res, user }) => res.send({ userDocumentId: user.userDocumentId, linkDatabaseId: user.linkDatabaseId, sharingDocumentId: user.sharingDocumentId }),
+            publicHandler: ({ res }) => res.send({ userDocumentId: '__guest__', linkDatabaseId: 3, sharingDocumentId: 2 }),
         });
 
         register({
             method: Method.GET,
-            subscription: "/getSharingDocumentId",
-            secureHandler: ({ res, user }) => res.send(user.sharingDocumentId)
+            subscription: '/getSharingDocumentId',
+            secureHandler: ({ res, user }) => res.send(user.sharingDocumentId),
+            publicHandler: ({ res }) => res.send(2),
         });
 
         register({
             method: Method.GET,
-            subscription: "/getLinkDatabaseId",
-            secureHandler: ({ res, user }) => res.send(user.linkDatabaseId)
+            subscription: '/getLinkDatabaseId',
+            secureHandler: ({ res, user }) => res.send(user.linkDatabaseId),
+            publicHandler: ({ res }) => res.send(3),
         });
 
         register({
             method: Method.GET,
-            subscription: "/getCurrentUser",
+            subscription: '/getCurrentUser',
             secureHandler: ({ res, user: { _id, email, cacheDocumentIds } }) => res.send(JSON.stringify({ id: _id, email, cacheDocumentIds })),
-            publicHandler: ({ res }) => res.send(JSON.stringify({ id: "__guest__", email: "" }))
+            publicHandler: ({ res }) => res.send(JSON.stringify({ id: '__guest__', email: 'guest' })),
         });
 
         register({
@@ -80,7 +81,7 @@ export default class UserManager extends ApiManager {
                 const validated = await new Promise<Opt<boolean>>(resolve => {
                     bcrypt.compare(curr_pass, user.password, (err, passwords_match) => {
                         if (err || !passwords_match) {
-                            result.error = [{ msg: "Incorrect current password" }];
+                            result.error = [{ msg: 'Incorrect current password' }];
                             res.send(result);
                             resolve(undefined);
                         } else {
@@ -93,10 +94,10 @@ export default class UserManager extends ApiManager {
                     return;
                 }
 
-                req.assert("new_pass", "Password must be at least 4 characters long").len({ min: 4 });
-                req.assert("new_confirm", "Passwords do not match").equals(new_pass);
+                req.assert('new_pass', 'Password must be at least 4 characters long').len({ min: 4 });
+                req.assert('new_confirm', 'Passwords do not match').equals(new_pass);
                 if (curr_pass === new_pass) {
-                    result.error = [{ msg: "Current and new password are the same" }];
+                    result.error = [{ msg: 'Current and new password are the same' }];
                 }
                 // was there error in validating new passwords?
                 if (req.validationErrors()) {
@@ -113,17 +114,17 @@ export default class UserManager extends ApiManager {
 
                 user.save(err => {
                     if (err) {
-                        result.error = [{ msg: "Error while saving new password" }];
+                        result.error = [{ msg: 'Error while saving new password' }];
                     }
                 });
 
                 res.send(result);
-            }
+            },
         });
 
         register({
             method: Method.GET,
-            subscription: "/activity",
+            subscription: '/activity',
             secureHandler: ({ res }) => {
                 const now = Date.now();
 
@@ -135,25 +136,23 @@ export default class UserManager extends ApiManager {
                     const socketPair = Array.from(WebSocket.socketMap).find(pair => pair[1] === user);
                     if (socketPair && !socketPair[0].disconnected) {
                         const duration = now - time;
-                        const target = (duration / 1000) < (60 * 5) ? activeTimes : inactiveTimes;
+                        const target = duration / 1000 < 60 * 5 ? activeTimes : inactiveTimes;
                         target.push({ user, duration });
                     }
                 }
 
-                const process = (target: { user: string, duration: number }[]) => {
+                const process = (target: { user: string; duration: number }[]) => {
                     const comparator = (first: ActivityUnit, second: ActivityUnit) => first.duration - second.duration;
                     const sorted = target.sort(comparator);
                     return sorted.map(({ user, duration }) => `${user} (${msToTime(duration)})`);
                 };
 
-                res.render("user_activity.pug", {
-                    title: "User Activity",
+                res.render('user_activity.pug', {
+                    title: 'User Activity',
                     active: process(activeTimes),
-                    inactive: process(inactiveTimes)
+                    inactive: process(inactiveTimes),
                 });
-            }
+            },
         });
-
     }
-
-}
\ No newline at end of file
+}
diff --git a/src/server/RouteManager.ts b/src/server/RouteManager.ts
index aa9bfcfa7..5683cd539 100644
--- a/src/server/RouteManager.ts
+++ b/src/server/RouteManager.ts
@@ -1,12 +1,12 @@
 import { cyan, green, red } from 'colors';
 import { Express, Request, Response } from 'express';
-import { AdminPriviliges } from ".";
-import { DashUserModel } from "./authentication/DashUserModel";
-import RouteSubscriber from "./RouteSubscriber";
+import { AdminPriviliges } from '.';
+import { DashUserModel } from './authentication/DashUserModel';
+import RouteSubscriber from './RouteSubscriber';
 
 export enum Method {
     GET,
-    POST
+    POST,
 }
 
 export interface CoreArguments {
@@ -33,13 +33,13 @@ const registered = new Map<string, Set<Method>>();
 
 enum RegistrationError {
     Malformed,
-    Duplicate
+    Duplicate,
 }
 
 export default class RouteManager {
     private server: Express;
     private _isRelease: boolean;
-    private failedRegistrations: { route: string, reason: RegistrationError }[] = [];
+    private failedRegistrations: { route: string; reason: RegistrationError }[] = [];
 
     public get isRelease() {
         return this._isRelease;
@@ -74,39 +74,42 @@ export default class RouteManager {
             }
             process.exit(1);
         } else {
-            console.log(green("all server routes have been successfully registered:"));
-            Array.from(registered.keys()).sort().forEach(route => console.log(cyan(route)));
+            console.log(green('all server routes have been successfully registered:'));
+            Array.from(registered.keys())
+                .sort()
+                .forEach(route => console.log(cyan(route)));
             console.log();
         }
-    }
+    };
 
     static routes: string[] = [];
     /**
-     * 
-     * @param initializer 
+     *
+     * @param initializer
      */
     addSupervisedRoute = (initializer: RouteInitializer): void => {
         const { method, subscription, secureHandler, publicHandler, errorHandler, requireAdminInRelease: requireAdmin } = initializer;
 
-        typeof (initializer.subscription) === "string" && RouteManager.routes.push(initializer.subscription);
+        typeof initializer.subscription === 'string' && RouteManager.routes.push(initializer.subscription);
         initializer.subscription instanceof RouteSubscriber && RouteManager.routes.push(initializer.subscription.root);
-        initializer.subscription instanceof Array && initializer.subscription.map(sub => {
-            typeof (sub) === "string" && RouteManager.routes.push(sub);
-            sub instanceof RouteSubscriber && RouteManager.routes.push(sub.root);
-        });
+        initializer.subscription instanceof Array &&
+            initializer.subscription.map(sub => {
+                typeof sub === 'string' && RouteManager.routes.push(sub);
+                sub instanceof RouteSubscriber && RouteManager.routes.push(sub.root);
+            });
         const isRelease = this._isRelease;
         const supervised = async (req: Request, res: Response) => {
             let user = req.user as Partial<DashUserModel> | undefined;
             const { originalUrl: target } = req;
-            if (process.env.DB === "MEM" && !user) {
-                user = { id: "guest", email: "", userDocumentId: "guestDocId" };
+            if (process.env.DB === 'MEM' && !user) {
+                user = { id: 'guest', email: 'guest', userDocumentId: '__guest__' };
             }
             const core = { req, res, isRelease };
             const tryExecute = async (toExecute: (args: any) => any | Promise<any>, args: any) => {
                 try {
                     await toExecute(args);
                 } catch (e) {
-                    console.log(red(target), user && ("email" in user) ? "<user logged out>" : undefined);
+                    console.log(red(target), user && 'email' in user ? '<user logged out>' : undefined);
                     if (errorHandler) {
                         errorHandler({ ...core, error: e });
                     } else {
@@ -119,7 +122,7 @@ export default class RouteManager {
                     if (AdminPriviliges.get(user.id)) {
                         AdminPriviliges.delete(user.id);
                     } else {
-                        return res.redirect(`/admin/${req.originalUrl.substring(1).replace("/", ":")}`);
+                        return res.redirect(`/admin/${req.originalUrl.substring(1).replace('/', ':')}`);
                     }
                 }
                 await tryExecute(secureHandler, { ...core, user });
@@ -128,10 +131,10 @@ export default class RouteManager {
                 if (publicHandler) {
                     await tryExecute(publicHandler, core);
                     if (!res.headersSent) {
-                        res.redirect("/login");
+                        // res.redirect("/login");
                     }
                 } else {
-                    res.redirect("/login");
+                    res.redirect('/login');
                 }
             }
             setTimeout(() => {
@@ -144,7 +147,7 @@ export default class RouteManager {
         };
         const subscribe = (subscriber: RouteSubscriber | string) => {
             let route: string;
-            if (typeof subscriber === "string") {
+            if (typeof subscriber === 'string') {
                 route = subscriber;
             } else {
                 route = subscriber.build;
@@ -152,7 +155,7 @@ export default class RouteManager {
             if (!/^\/$|^\/[A-Za-z\*]+(\/\:[A-Za-z?_\*]+)*$/g.test(route)) {
                 this.failedRegistrations.push({
                     reason: RegistrationError.Malformed,
-                    route
+                    route,
                 });
             } else {
                 const existing = registered.get(route);
@@ -160,7 +163,7 @@ export default class RouteManager {
                     if (existing.has(method)) {
                         this.failedRegistrations.push({
                             reason: RegistrationError.Duplicate,
-                            route
+                            route,
                         });
                         return;
                     }
@@ -184,15 +187,14 @@ export default class RouteManager {
         } else {
             subscribe(subscription);
         }
-    }
-
+    };
 }
 
 export const STATUS = {
     OK: 200,
     BAD_REQUEST: 400,
     EXECUTION_ERROR: 500,
-    PERMISSION_DENIED: 403
+    PERMISSION_DENIED: 403,
 };
 
 export function _error(res: Response, message: string, error?: any) {
diff --git a/src/server/authentication/AuthenticationManager.ts b/src/server/authentication/AuthenticationManager.ts
index 3622be4c5..52d876e95 100644
--- a/src/server/authentication/AuthenticationManager.ts
+++ b/src/server/authentication/AuthenticationManager.ts
@@ -1,4 +1,4 @@
-import { default as User, DashUserModel } from './DashUserModel';
+import { default as User, DashUserModel, initializeGuest } from './DashUserModel';
 import { Request, Response, NextFunction } from 'express';
 import * as passport from 'passport';
 import { IVerifyOptions } from 'passport-local';
@@ -30,6 +30,7 @@ export let getSignup = (req: Request, res: Response) => {
  * Create a new local account.
  */
 export let postSignup = (req: Request, res: Response, next: NextFunction) => {
+    const email = req.body.email as String;
     req.assert('email', 'Email is not valid').isEmail();
     req.assert('password', 'Password must be at least 4 characters long').len({ min: 4 });
     req.assert('confirmPassword', 'Passwords do not match').equals(req.body.password);
@@ -41,15 +42,14 @@ export let postSignup = (req: Request, res: Response, next: NextFunction) => {
         return res.redirect('/signup');
     }
 
-    const email = req.body.email as String;
     const password = req.body.password;
 
     const model = {
         email,
         password,
-        userDocumentId: Utils.GenerateGuid(),
-        sharingDocumentId: Utils.GenerateGuid(),
-        linkDatabaseId: Utils.GenerateGuid(),
+        userDocumentId: email === 'guest' ? '__guest__' : Utils.GenerateGuid(),
+        sharingDocumentId: email === 'guest' ? 2 : Utils.GenerateGuid(),
+        linkDatabaseId: email === 'guest' ? 3 : Utils.GenerateGuid(),
         cacheDocumentIds: '',
     } as Partial<DashUserModel>;
 
@@ -106,18 +106,22 @@ export let getLogin = (req: Request, res: Response) => {
  * On failure, redirect to signup page
  */
 export let postLogin = (req: Request, res: Response, next: NextFunction) => {
-    req.assert('email', 'Email is not valid').isEmail();
-    req.assert('password', 'Password cannot be blank').notEmpty();
-    req.sanitize('email').normalizeEmail({ gmail_remove_dots: false });
-
-    const errors = req.validationErrors();
+    if (req.body.email === '') {
+        User.findOne({ email: 'guest' }, (err: any, user: DashUserModel) => !user && initializeGuest());
+        req.body.email = 'guest';
+        req.body.password = 'guest';
+    } else {
+        req.assert('email', 'Email is not valid').isEmail();
+        req.assert('password', 'Password cannot be blank').notEmpty();
+        req.sanitize('email').normalizeEmail({ gmail_remove_dots: false });
+    }
 
-    if (errors) {
+    if (req.validationErrors()) {
         req.flash('errors', 'Unable to login at this time. Please try again.');
         return res.redirect('/signup');
     }
 
-    passport.authenticate('local', (err: Error, user: DashUserModel, _info: IVerifyOptions) => {
+    const callback = (err: Error, user: DashUserModel, _info: IVerifyOptions) => {
         if (err) {
             next(err);
             return;
@@ -132,7 +136,8 @@ export let postLogin = (req: Request, res: Response, next: NextFunction) => {
             }
             tryRedirectToTarget(req, res);
         });
-    })(req, res, next);
+    };
+    setTimeout(() => passport.authenticate('local', callback)(req, res, next), 500);
 };
 
 /**
diff --git a/src/server/authentication/DashUserModel.ts b/src/server/authentication/DashUserModel.ts
index bee28b96d..a1883beab 100644
--- a/src/server/authentication/DashUserModel.ts
+++ b/src/server/authentication/DashUserModel.ts
@@ -1,13 +1,13 @@
 //@ts-ignore
-import * as bcrypt from "bcrypt-nodejs";
+import * as bcrypt from 'bcrypt-nodejs';
 //@ts-ignore
 import * as mongoose from 'mongoose';
 
 export type DashUserModel = mongoose.Document & {
-    email: String,
-    password: string,
-    passwordResetToken?: string,
-    passwordResetExpires?: Date,
+    email: String;
+    password: string;
+    passwordResetToken?: string;
+    passwordResetExpires?: Date;
 
     userDocumentId: string;
     sharingDocumentId: string;
@@ -15,66 +15,74 @@ export type DashUserModel = mongoose.Document & {
     cacheDocumentIds: string;
 
     profile: {
-        name: string,
-        gender: string,
-        location: string,
-        website: string,
-        picture: string
-    },
+        name: string;
+        gender: string;
+        location: string;
+        website: string;
+        picture: string;
+    };
 
-    comparePassword: comparePasswordFunction,
+    comparePassword: comparePasswordFunction;
 };
 
 type comparePasswordFunction = (candidatePassword: string, cb: (err: any, isMatch: any) => {}) => void;
 
 export type AuthToken = {
-    accessToken: string,
-    kind: string
+    accessToken: string;
+    kind: string;
 };
 
-const userSchema = new mongoose.Schema({
-    email: String,
-    password: String,
-    passwordResetToken: String,
-    passwordResetExpires: Date,
+const userSchema = new mongoose.Schema(
+    {
+        email: String,
+        password: String,
+        passwordResetToken: String,
+        passwordResetExpires: Date,
 
-    userDocumentId: String,    // id that identifies a document which hosts all of a user's account data
-    sharingDocumentId: String, // id that identifies a document that stores documents shared to a user, their user color, and any additional info needed to communicate between users
-    linkDatabaseId: String,
-    cacheDocumentIds: String, // set of document ids to retreive on startup
+        userDocumentId: String, // id that identifies a document which hosts all of a user's account data
+        sharingDocumentId: String, // id that identifies a document that stores documents shared to a user, their user color, and any additional info needed to communicate between users
+        linkDatabaseId: String,
+        cacheDocumentIds: String, // set of document ids to retreive on startup
 
-    facebook: String,
-    twitter: String,
-    google: String,
+        facebook: String,
+        twitter: String,
+        google: String,
 
-    profile: {
-        name: String,
-        gender: String,
-        location: String,
-        website: String,
-        picture: String
-    }
-}, { timestamps: true });
+        profile: {
+            name: String,
+            gender: String,
+            location: String,
+            website: String,
+            picture: String,
+        },
+    },
+    { timestamps: true }
+);
 
 /**
  * Password hash middleware.
  */
-userSchema.pre("save", function save(next) {
+userSchema.pre('save', function save(next) {
     const user = this as DashUserModel;
-    if (!user.isModified("password")) {
+    if (!user.isModified('password')) {
         return next();
     }
     bcrypt.genSalt(10, (err: any, salt: string) => {
         if (err) {
             return next(err);
         }
-        bcrypt.hash(user.password, salt, () => void {}, (err: mongoose.Error, hash: string) => {
-            if (err) {
-                return next(err);
+        bcrypt.hash(
+            user.password,
+            salt,
+            () => void {},
+            (err: mongoose.Error, hash: string) => {
+                if (err) {
+                    return next(err);
+                }
+                user.password = hash;
+                next();
             }
-            user.password = hash;
-            next();
-        });
+        );
     });
 });
 
@@ -88,5 +96,15 @@ const comparePassword: comparePasswordFunction = function (this: DashUserModel,
 
 userSchema.methods.comparePassword = comparePassword;
 
-const User = mongoose.model("User", userSchema);
-export default User;
\ No newline at end of file
+const User = mongoose.model('User', userSchema);
+export function initializeGuest() {
+    new User({
+        email: 'guest',
+        password: 'guest',
+        userDocumentId: '__guest__',
+        sharingDocumentId: '2',
+        linkDatabaseId: '3',
+        cacheDocumentIds: '',
+    }).save();
+}
+export default User;
diff --git a/src/server/index.ts b/src/server/index.ts
index f8c32103b..6e6bde3cb 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -1,35 +1,35 @@
 require('dotenv').config();
-import { yellow } from "colors";
+import { yellow } from 'colors';
 import * as mobileDetect from 'mobile-detect';
 import * as path from 'path';
 import * as qs from 'query-string';
-import { log_execution } from "./ActionUtilities";
-import DeleteManager from "./ApiManagers/DeleteManager";
+import { log_execution } from './ActionUtilities';
+import DeleteManager from './ApiManagers/DeleteManager';
 import DownloadManager from './ApiManagers/DownloadManager';
-import GeneralGoogleManager from "./ApiManagers/GeneralGoogleManager";
-import GooglePhotosManager from "./ApiManagers/GooglePhotosManager";
-import PDFManager from "./ApiManagers/PDFManager";
+import GeneralGoogleManager from './ApiManagers/GeneralGoogleManager';
+import GooglePhotosManager from './ApiManagers/GooglePhotosManager';
+import PDFManager from './ApiManagers/PDFManager';
 import { SearchManager } from './ApiManagers/SearchManager';
-import SessionManager from "./ApiManagers/SessionManager";
-import UploadManager from "./ApiManagers/UploadManager";
+import SessionManager from './ApiManagers/SessionManager';
+import UploadManager from './ApiManagers/UploadManager';
 import UserManager from './ApiManagers/UserManager';
 import UtilManager from './ApiManagers/UtilManager';
 import { GoogleCredentialsLoader, SSL } from './apis/google/CredentialsLoader';
-import { GoogleApiServerUtils } from "./apis/google/GoogleApiServerUtils";
-import { DashSessionAgent } from "./DashSession/DashSessionAgent";
-import { AppliedSessionAgent } from "./DashSession/Session/agents/applied_session_agent";
+import { GoogleApiServerUtils } from './apis/google/GoogleApiServerUtils';
+import { DashSessionAgent } from './DashSession/DashSessionAgent';
+import { AppliedSessionAgent } from './DashSession/Session/agents/applied_session_agent';
 import { DashUploadUtils } from './DashUploadUtils';
 import { Database } from './database';
-import { Logger } from "./ProcessFactory";
+import { Logger } from './ProcessFactory';
 import RouteManager, { Method, PublicHandler } from './RouteManager';
 import RouteSubscriber from './RouteSubscriber';
 import initializeServer, { resolvedPorts } from './server_Initialization';
 
 export const AdminPriviliges: Map<string, boolean> = new Map();
-export const onWindows = process.platform === "win32";
+export const onWindows = process.platform === 'win32';
 export let sessionAgent: AppliedSessionAgent;
-export const publicDirectory = path.resolve(__dirname, "public");
-export const filesDirectory = path.resolve(publicDirectory, "files");
+export const publicDirectory = path.resolve(__dirname, 'public');
+export const filesDirectory = path.resolve(publicDirectory, 'files');
 
 /**
  * These are the functions run before the server starts
@@ -43,11 +43,11 @@ async function preliminaryFunctions() {
     await GoogleCredentialsLoader.loadCredentials();
     SSL.loadCredentials();
     GoogleApiServerUtils.processProjectCredentials();
-    if (process.env.DB !== "MEM") {
+    if (process.env.DB !== 'MEM') {
         await log_execution({
-            startMessage: "attempting to initialize mongodb connection",
-            endMessage: "connection outcome determined",
-            action: Database.tryInitializeConnection
+            startMessage: 'attempting to initialize mongodb connection',
+            endMessage: 'connection outcome determined',
+            action: Database.tryInitializeConnection,
         });
     }
 }
@@ -56,27 +56,16 @@ async function preliminaryFunctions() {
  * Either clustered together as an API manager
  * or individually referenced below, by the completion
  * of this function's execution, all routes will
- * be registered on the server 
+ * be registered on the server
  * @param router the instance of the route manager
  * that will manage the registration of new routes
  * with the server
  */
 function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }: RouteManager) {
-    const managers = [
-        new SessionManager(),
-        new UserManager(),
-        new UploadManager(),
-        new DownloadManager(),
-        new SearchManager(),
-        new PDFManager(),
-        new DeleteManager(),
-        new UtilManager(),
-        new GeneralGoogleManager(),
-        new GooglePhotosManager(),
-    ];
+    const managers = [new SessionManager(), new UserManager(), new UploadManager(), new DownloadManager(), new SearchManager(), new PDFManager(), new DeleteManager(), new UtilManager(), new GeneralGoogleManager(), new GooglePhotosManager()];
 
     // initialize API Managers
-    console.log(yellow("\nregistering server routes..."));
+    console.log(yellow('\nregistering server routes...'));
     managers.forEach(manager => manager.register(addSupervisedRoute));
 
     /**
@@ -84,88 +73,87 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
      */
     addSupervisedRoute({
         method: Method.GET,
-        subscription: "/",
-        secureHandler: ({ res }) => res.redirect("/home")
+        subscription: '/',
+        secureHandler: ({ res }) => res.redirect('/home'),
     });
 
-
     addSupervisedRoute({
         method: Method.GET,
-        subscription: "/serverHeartbeat",
-        secureHandler: ({ res }) => res.send(true)
+        subscription: '/serverHeartbeat',
+        secureHandler: ({ res }) => res.send(true),
     });
 
     addSupervisedRoute({
         method: Method.GET,
-        subscription: "/resolvedPorts",
-        secureHandler: ({ res }) => res.send(resolvedPorts)
+        subscription: '/resolvedPorts',
+        secureHandler: ({ res }) => res.send(resolvedPorts),
+        publicHandler: ({ res }) => res.send(resolvedPorts),
     });
 
     const serve: PublicHandler = ({ req, res }) => {
-        const detector = new mobileDetect(req.headers['user-agent'] || "");
+        const detector = new mobileDetect(req.headers['user-agent'] || '');
         const filename = detector.mobile() !== null ? 'mobile/image.html' : 'index.html';
         res.sendFile(path.join(__dirname, '../../deploy/' + filename));
     };
 
     /**
-     * Serves a simple password input box for any 
+     * Serves a simple password input box for any
      */
     addSupervisedRoute({
         method: Method.GET,
-        subscription: new RouteSubscriber("admin").add("previous_target"),
+        subscription: new RouteSubscriber('admin').add('previous_target'),
         secureHandler: ({ res, isRelease }) => {
             const { PASSWORD } = process.env;
             if (!(isRelease && PASSWORD)) {
-                return res.redirect("/home");
+                return res.redirect('/home');
             }
-            res.render("admin.pug", { title: "Enter Administrator Password" });
-        }
+            res.render('admin.pug', { title: 'Enter Administrator Password' });
+        },
     });
 
     addSupervisedRoute({
         method: Method.POST,
-        subscription: new RouteSubscriber("admin").add("previous_target"),
+        subscription: new RouteSubscriber('admin').add('previous_target'),
         secureHandler: async ({ req, res, isRelease, user: { id } }) => {
             const { PASSWORD } = process.env;
             if (!(isRelease && PASSWORD)) {
-                return res.redirect("/home");
+                return res.redirect('/home');
             }
             const { password } = req.body;
             const { previous_target } = req.params;
             let redirect: string;
             if (password === PASSWORD) {
                 AdminPriviliges.set(id, true);
-                redirect = `/${previous_target.replace(":", "/")}`;
+                redirect = `/${previous_target.replace(':', '/')}`;
             } else {
                 redirect = `/admin/${previous_target}`;
             }
             res.redirect(redirect);
-        }
+        },
     });
 
     addSupervisedRoute({
         method: Method.GET,
-        subscription: ["/home", new RouteSubscriber("doc").add("docId")],
+        subscription: ['/home', new RouteSubscriber('doc').add('docId')],
         secureHandler: serve,
         publicHandler: ({ req, res, ...remaining }) => {
             const { originalUrl: target } = req;
-            const sharing = qs.parse(qs.extract(req.originalUrl), { sort: false }).sharing === "true";
-            const docAccess = target.startsWith("/doc/");
+            const sharing = qs.parse(qs.extract(req.originalUrl), { sort: false }).sharing === 'true';
+            const docAccess = target.startsWith('/doc/');
             // since this is the public handler, there's no meaning of '/home' to speak of
             // since there's no user logged in, so the only viable operation
             // for a guest is to look at a shared document
-            if (sharing && docAccess) {
+            if (docAccess) {
                 serve({ req, res, ...remaining });
             } else {
-                res.redirect("/login");
+                res.redirect('/login');
             }
-        }
+        },
     });
 
     logRegistrationOutcome();
 }
 
-
 /**
  * This function can be used in two different ways. If not in release mode,
  * this is simply the logic that is invoked to start the server. In release mode,
@@ -174,9 +162,9 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
  */
 export async function launchServer() {
     await log_execution({
-        startMessage: "\nstarting execution of preliminary functions",
-        endMessage: "completed preliminary functions\n",
-        action: preliminaryFunctions
+        startMessage: '\nstarting execution of preliminary functions',
+        endMessage: 'completed preliminary functions\n',
+        action: preliminaryFunctions,
     });
     await initializeServer(routeSetter);
 }
diff --git a/src/server/websocket.ts b/src/server/websocket.ts
index 1b7f5919f..9b91a35a6 100644
--- a/src/server/websocket.ts
+++ b/src/server/websocket.ts
@@ -1,24 +1,24 @@
-import * as express from "express";
-import { blue, green } from "colors";
-import { createServer, Server } from "https";
-import { networkInterfaces } from "os";
+import { blue } from 'colors';
+import * as express from 'express';
+import { createServer, Server } from 'https';
+import { networkInterfaces } from 'os';
 import * as sio from 'socket.io';
-import { Socket } from "socket.io";
-import { Utils } from "../Utils";
+import { Socket } from 'socket.io';
+import { Opt } from '../fields/Doc';
+import { Utils } from '../Utils';
 import { logPort } from './ActionUtilities';
-import { timeMap } from "./ApiManagers/UserManager";
-import { GoogleCredentialsLoader, SSL } from "./apis/google/CredentialsLoader";
-import YoutubeApi from "./apis/youtube/youtubeApiSample";
-import { Client } from "./Client";
-import { Database } from "./database";
-import { DocumentsCollection } from "./IDatabase";
-import { Diff, GestureContent, MessageStore, MobileDocumentUploadContent, MobileInkOverlayContent, Transferable, Types, UpdateMobileInkOverlayPositionContent, YoutubeQueryInput, YoutubeQueryTypes } from "./Message";
-import { Search } from "./Search";
+import { timeMap } from './ApiManagers/UserManager';
+import { GoogleCredentialsLoader, SSL } from './apis/google/CredentialsLoader';
+import YoutubeApi from './apis/youtube/youtubeApiSample';
+import { initializeGuest } from './authentication/DashUserModel';
+import { Client } from './Client';
+import { Database } from './database';
+import { DocumentsCollection } from './IDatabase';
+import { Diff, GestureContent, MessageStore, MobileDocumentUploadContent, MobileInkOverlayContent, Transferable, Types, UpdateMobileInkOverlayPositionContent, YoutubeQueryInput, YoutubeQueryTypes } from './Message';
+import { Search } from './Search';
 import { resolvedPorts } from './server_Initialization';
-import { Opt } from "../fields/Doc";
 
 export namespace WebSocket {
-
     export let _socket: Socket;
     const clients: { [key: string]: Client } = {};
     export const socketMap = new Map<SocketIO.Socket, string>();
@@ -32,14 +32,14 @@ export namespace WebSocket {
                 resolvedPorts.socket = Number(socketPort);
             }
             let socketEndpoint: Opt<Server>;
-            await new Promise<void>(resolve => socketEndpoint = createServer(SSL.Credentials, app).listen(resolvedPorts.socket, resolve));
+            await new Promise<void>(resolve => (socketEndpoint = createServer(SSL.Credentials, app).listen(resolvedPorts.socket, resolve)));
             io = sio(socketEndpoint!, SSL.Credentials as any);
         } else {
             io = sio().listen(resolvedPorts.socket);
         }
-        logPort("websocket", resolvedPorts.socket);
+        logPort('websocket', resolvedPorts.socket);
 
-        io.on("connection", function (socket: Socket) {
+        io.on('connection', function (socket: Socket) {
             _socket = socket;
             socket.use((_packet, next) => {
                 const userEmail = socketMap.get(socket);
@@ -70,14 +70,14 @@ export namespace WebSocket {
                     socket.join(room);
                     console.log('Client ID ' + socket.id + ' created room ' + room);
                     socket.emit('created', room, socket.id);
-
                 } else if (numClients === 1) {
                     console.log('Client ID ' + socket.id + ' joined room ' + room);
                     socket.in(room).emit('join', room);
                     socket.join(room);
                     socket.emit('joined', room, socket.id);
                     socket.in(room).emit('ready');
-                } else { // max two clients
+                } else {
+                    // max two clients
                     socket.emit('full', room);
                 }
             });
@@ -97,10 +97,10 @@ export namespace WebSocket {
                 console.log('received bye');
             });
 
-            Utils.Emit(socket, MessageStore.Foo, "handshooken");
+            Utils.Emit(socket, MessageStore.Foo, 'handshooken');
 
             Utils.AddServerHandler(socket, MessageStore.Bar, guid => barReceived(socket, guid));
-            Utils.AddServerHandler(socket, MessageStore.SetField, (args) => setField(socket, args));
+            Utils.AddServerHandler(socket, MessageStore.SetField, args => setField(socket, args));
             Utils.AddServerHandlerCallback(socket, MessageStore.GetField, getField);
             Utils.AddServerHandlerCallback(socket, MessageStore.GetFields, getFields);
             if (isRelease) {
@@ -126,26 +126,26 @@ export namespace WebSocket {
              */
 
             disconnect = () => {
-                socket.broadcast.emit("connection_terminated", Date.now());
+                socket.broadcast.emit('connection_terminated', Date.now());
                 socket.disconnect(true);
             };
         });
     }
 
     function processGesturePoints(socket: Socket, content: GestureContent) {
-        socket.broadcast.emit("receiveGesturePoints", content);
+        socket.broadcast.emit('receiveGesturePoints', content);
     }
 
     function processOverlayTrigger(socket: Socket, content: MobileInkOverlayContent) {
-        socket.broadcast.emit("receiveOverlayTrigger", content);
+        socket.broadcast.emit('receiveOverlayTrigger', content);
     }
 
     function processUpdateOverlayPosition(socket: Socket, content: UpdateMobileInkOverlayPositionContent) {
-        socket.broadcast.emit("receiveUpdateOverlayPosition", content);
+        socket.broadcast.emit('receiveUpdateOverlayPosition', content);
     }
 
     function processMobileDocumentUpload(socket: Socket, content: MobileDocumentUploadContent) {
-        socket.broadcast.emit("receiveMobileDocumentUpload", content);
+        socket.broadcast.emit('receiveMobileDocumentUpload', content);
     }
 
     function HandleYoutubeQuery([query, callback]: [YoutubeQueryInput, (result?: any[]) => void]) {
@@ -165,27 +165,22 @@ export namespace WebSocket {
         const target: string[] = [];
         onlyFields && target.push(DocumentsCollection);
         await Database.Instance.dropSchema(...target);
-        if (process.env.DISABLE_SEARCH !== "true") {
+        if (process.env.DISABLE_SEARCH !== 'true') {
             await Search.clear();
         }
+        initializeGuest();
     }
 
     function barReceived(socket: SocketIO.Socket, userEmail: string) {
         clients[userEmail] = new Client(userEmail.toString());
         const currentdate = new Date();
-        const datetime = currentdate.getDate() + "/"
-            + (currentdate.getMonth() + 1) + "/"
-            + currentdate.getFullYear() + " @ "
-            + currentdate.getHours() + ":"
-            + currentdate.getMinutes() + ":"
-            + currentdate.getSeconds();
+        const datetime = currentdate.getDate() + '/' + (currentdate.getMonth() + 1) + '/' + currentdate.getFullYear() + ' @ ' + currentdate.getHours() + ':' + currentdate.getMinutes() + ':' + currentdate.getSeconds();
         console.log(blue(`user ${userEmail} has connected to the web socket at: ${datetime}`));
-        socketMap.set(socket, userEmail + " at " + datetime);
+        socketMap.set(socket, userEmail + ' at ' + datetime);
     }
 
     function getField([id, callback]: [string, (result?: Transferable) => void]) {
-        Database.Instance.getDocument(id, (result?: Transferable) =>
-            callback(result ? result : undefined));
+        Database.Instance.getDocument(id, (result?: Transferable) => callback(result ? result : undefined));
     }
 
     function getFields([ids, callback]: [string[], (result: Transferable[]) => void]) {
@@ -193,9 +188,9 @@ export namespace WebSocket {
     }
 
     function setField(socket: Socket, newValue: Transferable) {
-        Database.Instance.update(newValue.id, newValue, () =>
-            socket.broadcast.emit(MessageStore.SetField.Message, newValue));  // broadcast set value to all other clients
-        if (newValue.type === Types.Text) {  // if the newValue has sring type, then it's suitable for searching -- pass it to SOLR
+        Database.Instance.update(newValue.id, newValue, () => socket.broadcast.emit(MessageStore.SetField.Message, newValue)); // broadcast set value to all other clients
+        if (newValue.type === Types.Text) {
+            // if the newValue has sring type, then it's suitable for searching -- pass it to SOLR
             Search.updateDocument({ id: newValue.id, data: { set: (newValue as any).data } });
         }
     }
@@ -213,34 +208,36 @@ export namespace WebSocket {
         Database.Instance.getDocuments(ids, callback);
     }
 
-    const suffixMap: { [type: string]: (string | [string, string | ((json: any) => any)]) } = {
-        "number": "_n",
-        "string": "_t",
-        "boolean": "_b",
-        "image": ["_t", "url"],
-        "video": ["_t", "url"],
-        "pdf": ["_t", "url"],
-        "audio": ["_t", "url"],
-        "web": ["_t", "url"],
-        "map": ["_t", "url"],
-        "script": ["_t", value => value.script.originalScript],
-        "RichTextField": ["_t", value => value.Text],
-        "date": ["_d", value => new Date(value.date).toISOString()],
-        "proxy": ["_i", "fieldId"],
-        "list": ["_l", list => {
-            const results = [];
-            for (const value of list.fields) {
-                const term = ToSearchTerm(value);
-                if (term) {
-                    results.push(term.value);
+    const suffixMap: { [type: string]: string | [string, string | ((json: any) => any)] } = {
+        number: '_n',
+        string: '_t',
+        boolean: '_b',
+        image: ['_t', 'url'],
+        video: ['_t', 'url'],
+        pdf: ['_t', 'url'],
+        audio: ['_t', 'url'],
+        web: ['_t', 'url'],
+        map: ['_t', 'url'],
+        script: ['_t', value => value.script.originalScript],
+        RichTextField: ['_t', value => value.Text],
+        date: ['_d', value => new Date(value.date).toISOString()],
+        proxy: ['_i', 'fieldId'],
+        list: [
+            '_l',
+            list => {
+                const results = [];
+                for (const value of list.fields) {
+                    const term = ToSearchTerm(value);
+                    if (term) {
+                        results.push(term.value);
+                    }
                 }
-            }
-            return results.length ? results : null;
-        }]
+                return results.length ? results : null;
+            },
+        ],
     };
 
-    function ToSearchTerm(val: any): { suffix: string, value: any } | undefined {
-
+    function ToSearchTerm(val: any): { suffix: string; value: any } | undefined {
         if (val === null || val === undefined) {
             return;
         }
@@ -252,69 +249,79 @@ export namespace WebSocket {
         }
         if (Array.isArray(suffix)) {
             const accessor = suffix[1];
-            if (typeof accessor === "function") {
+            if (typeof accessor === 'function') {
                 val = accessor(val);
             } else {
                 val = val[accessor];
-
             }
             suffix = suffix[0];
-
         }
         return { suffix, value: val };
     }
 
     function getSuffix(value: string | [string, any]): string {
-        return typeof value === "string" ? value : value[0];
+        return typeof value === 'string' ? value : value[0];
     }
 
     function addToListField(socket: Socket, diff: Diff, curListItems?: Transferable): void {
-        diff.diff.$set = diff.diff.$addToSet; delete diff.diff.$addToSet;// convert add to set to a query of the current fields, and then a set of the composition of the new fields with the old ones
+        diff.diff.$set = diff.diff.$addToSet;
+        delete diff.diff.$addToSet; // convert add to set to a query of the current fields, and then a set of the composition of the new fields with the old ones
         const updatefield = Array.from(Object.keys(diff.diff.$set))[0];
         const newListItems = diff.diff.$set[updatefield]?.fields;
         if (!newListItems) {
-            console.log("Error: addToListField - no new list items");
+            console.log('Error: addToListField - no new list items');
             return;
         }
-        const curList = (curListItems as any)?.fields?.[updatefield.replace("fields.", "")]?.fields.filter((item: any) => item !== undefined) || [];
-        diff.diff.$set[updatefield].fields = [...curList, ...newListItems];//, ...newListItems.filter((newItem: any) => newItem === null || !curList.some((curItem: any) => curItem.fieldId ? curItem.fieldId === newItem.fieldId : curItem.heading ? curItem.heading === newItem.heading : curItem === newItem))];
+        const curList = (curListItems as any)?.fields?.[updatefield.replace('fields.', '')]?.fields.filter((item: any) => item !== undefined) || [];
+        diff.diff.$set[updatefield].fields = [...curList, ...newListItems]; //, ...newListItems.filter((newItem: any) => newItem === null || !curList.some((curItem: any) => curItem.fieldId ? curItem.fieldId === newItem.fieldId : curItem.heading ? curItem.heading === newItem.heading : curItem === newItem))];
         const sendBack = diff.diff.length !== diff.diff.$set[updatefield].fields.length;
         delete diff.diff.length;
-        Database.Instance.update(diff.id, diff.diff,
+        Database.Instance.update(
+            diff.id,
+            diff.diff,
             () => {
                 if (sendBack) {
-                    console.log("Warning: list modified during update. Composite list is being returned.");
+                    console.log('Warning: list modified during update. Composite list is being returned.');
                     const id = socket.id;
-                    socket.id = "";
+                    socket.id = '';
                     socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
                     socket.id = id;
                 } else socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
                 dispatchNextOp(diff.id);
-            }, false);
+            },
+            false
+        );
     }
 
     function remFromListField(socket: Socket, diff: Diff, curListItems?: Transferable): void {
-        diff.diff.$set = diff.diff.$remFromSet; delete diff.diff.$remFromSet;
+        diff.diff.$set = diff.diff.$remFromSet;
+        delete diff.diff.$remFromSet;
         const updatefield = Array.from(Object.keys(diff.diff.$set))[0];
         const remListItems = diff.diff.$set[updatefield].fields;
-        const curList = (curListItems as any)?.fields?.[updatefield.replace("fields.", "")]?.fields.filter((f: any) => f !== null) || [];
-        diff.diff.$set[updatefield].fields = curList?.filter((curItem: any) => !remListItems.some((remItem: any) => remItem.fieldId ? remItem.fieldId === curItem.fieldId : remItem.heading ? remItem.heading === curItem.heading : remItem === curItem));
+        const curList = (curListItems as any)?.fields?.[updatefield.replace('fields.', '')]?.fields.filter((f: any) => f !== null) || [];
+        diff.diff.$set[updatefield].fields = curList?.filter(
+            (curItem: any) => !remListItems.some((remItem: any) => (remItem.fieldId ? remItem.fieldId === curItem.fieldId : remItem.heading ? remItem.heading === curItem.heading : remItem === curItem))
+        );
         const sendBack = diff.diff.length !== diff.diff.$set[updatefield].fields.length;
         delete diff.diff.length;
-        Database.Instance.update(diff.id, diff.diff,
+        Database.Instance.update(
+            diff.id,
+            diff.diff,
             () => {
                 if (sendBack) {
-                    console.log("SEND BACK");
+                    console.log('SEND BACK');
                     const id = socket.id;
-                    socket.id = "";
+                    socket.id = '';
                     socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
                     socket.id = id;
                 } else socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
                 dispatchNextOp(diff.id);
-            }, false);
+            },
+            false
+        );
     }
 
-    const pendingOps = new Map<string, { diff: Diff, socket: Socket }[]>();
+    const pendingOps = new Map<string, { diff: Diff; socket: Socket }[]>();
 
     function dispatchNextOp(id: string) {
         const next = pendingOps.get(id)!.shift();
@@ -341,7 +348,7 @@ export namespace WebSocket {
     function UpdateField(socket: Socket, diff: Diff) {
         if (CurUser !== socketMap.get(socket)) {
             CurUser = socketMap.get(socket);
-            console.log("Switch User: " + CurUser);
+            console.log('Switch User: ' + CurUser);
         }
         if (pendingOps.has(diff.id)) {
             pendingOps.get(diff.id)!.push({ diff, socket });
@@ -357,24 +364,25 @@ export namespace WebSocket {
         return GetRefFieldLocal([diff.id, (result?: Transferable) => SetField(socket, diff, result)]);
     }
     function SetField(socket: Socket, diff: Diff, curListItems?: Transferable) {
-        Database.Instance.update(diff.id, diff.diff,
-            () => socket.broadcast.emit(MessageStore.UpdateField.Message, diff), false);
+        Database.Instance.update(diff.id, diff.diff, () => socket.broadcast.emit(MessageStore.UpdateField.Message, diff), false);
         const docfield = diff.diff.$set || diff.diff.$unset;
         if (docfield) {
             const update: any = { id: diff.id };
             let dynfield = false;
             for (let key in docfield) {
-                if (!key.startsWith("fields.")) continue;
+                if (!key.startsWith('fields.')) continue;
                 dynfield = true;
                 const val = docfield[key];
                 key = key.substring(7);
-                Object.values(suffixMap).forEach(suf => { update[key + getSuffix(suf)] = { set: null }; });
+                Object.values(suffixMap).forEach(suf => {
+                    update[key + getSuffix(suf)] = { set: null };
+                });
                 const term = ToSearchTerm(val);
                 if (term !== undefined) {
                     const { suffix, value } = term;
                     update[key + suffix] = { set: value };
                     if (key.endsWith('lastModified')) {
-                        update["lastModified" + suffix] = value;
+                        update['lastModified' + suffix] = value;
                     }
                 }
             }
@@ -403,6 +411,4 @@ export namespace WebSocket {
     function CreateField(newValue: any) {
         Database.Instance.insert(newValue);
     }
-
 }
-
diff --git a/views/login.pug b/views/login.pug
index 98816e9c8..71c6933be 100644
--- a/views/login.pug
+++ b/views/login.pug
@@ -15,10 +15,10 @@ block content
                 h3.auth_header Log In
                 .form-group
                     .col-sm-7
-                    input.form-control(type='email', name='email', id='email', placeholder='Email', autofocus, required)
+                    input.form-control(type='email', name='email', id='email', placeholder='Email (or blank for "guest")', autofocus)
                 .form-group
                     .col-sm-7
-                    input.form-control(type='password', name='password', id='password', placeholder='Password', required)
+                    input.form-control(type='password', name='password', id='password', placeholder='Password')
                 .form-group
                     .col-sm-offset-3.col-sm-7
                     button.btn.btn-success(id='submit', type='submit')
-- 
cgit v1.2.3-70-g09d2

