Repository: concord-consortium/codap
Pull request #1909: Codap 50 user entry modal
Author: login: eireland
Labels: name: v3
description: CODAP v3
name: run regression
Created: 2025-04-15T15:54:00Z | Merged: 2025-04-18T16:31:46Z

Adds the user entry modal for when there are no url params and no current open document.
User can drag document to modal and modal overlay to import a local file. But the yellow highlight around the app to indicate that you are importing a local file on drop is not present.


Diff:
diff --git a/v3/cypress/e2e/attribute-types.spec.ts b/v3/cypress/e2e/attribute-types.spec.ts
index 148328eb7b..78da837aaa 100644
--- a/v3/cypress/e2e/attribute-types.spec.ts
+++ b/v3/cypress/e2e/attribute-types.spec.ts
@@ -7,7 +7,7 @@ context("attribute types", () => {
     const url = `${Cypress.config("index")}`
     cy.visit(url)
     cy.wait(3000)
-    cfm.openLocalDoc(filename)
+    cfm.openLocalDocWithUserEntry(filename)
   })
 
   describe("attribute types are rendered correctly", () => {
diff --git a/v3/cypress/e2e/cfm.spec.ts b/v3/cypress/e2e/cfm.spec.ts
index ee989a3cec..4f98650513 100644
--- a/v3/cypress/e2e/cfm.spec.ts
+++ b/v3/cypress/e2e/cfm.spec.ts
@@ -4,7 +4,7 @@ import { TableTileElements as table } from "../support/elements/table-tile"
 
 context("CloudFileManager", () => {
   function visitEmptyCodap()  {
-    const queryParams = "?mouseSensor"
+    const queryParams = "?mouseSensor&noEntryModal"
     const url = `${Cypress.config("index")}${queryParams}`
     cy.visit(url)
   }
diff --git a/v3/cypress/e2e/formula/formula-component.spec.ts b/v3/cypress/e2e/formula/formula-component.spec.ts
index f09bec6cf7..86e114da21 100644
--- a/v3/cypress/e2e/formula/formula-component.spec.ts
+++ b/v3/cypress/e2e/formula/formula-component.spec.ts
@@ -86,6 +86,7 @@ context("Formula Engine", () => {
     // TODO: update test when insert cases on input row is implemnted
     it("Formula in a new dataset", () => {
       fh.visitURL("")
+      cy.get("#user-entry-drop-overlay").type("{esc}")
       table.createNewTableFromToolShelf()
       table.getGridCell(2, 2).dblclick()
       table.getGridCell(2, 2).find("input").type("Sloth{enter}")
diff --git a/v3/cypress/e2e/import-v2.spec.ts b/v3/cypress/e2e/import-v2.spec.ts
index 3df4e08635..4d6770086e 100644
--- a/v3/cypress/e2e/import-v2.spec.ts
+++ b/v3/cypress/e2e/import-v2.spec.ts
@@ -3,7 +3,7 @@ import { WebViewTileElements as webView } from "../support/elements/web-view-til
 
 context("import codap v2 documents", () => {
   beforeEach(function () {
-    const url = `${Cypress.config("index")}?mouseSensor`
+    const url = `${Cypress.config("index")}?mouseSensor&noEntryModal`
     cy.visit(url)
   })
   it('will load plugins from v2 documents', () => {
diff --git a/v3/cypress/e2e/map.spec.ts b/v3/cypress/e2e/map.spec.ts
index 72813fafaa..ef0c289a66 100644
--- a/v3/cypress/e2e/map.spec.ts
+++ b/v3/cypress/e2e/map.spec.ts
@@ -18,7 +18,7 @@ const arrayOfAttributes = ["Category", "Educ_Tertiary_Perc", "Inversions"]
 
 context("Map UI", () => {
   beforeEach(function () {
-    const url = `${Cypress.config("index")}?mouseSensor&noComponentAnimation`
+    const url = `${Cypress.config("index")}?mouseSensor&noComponentAnimation&noEntryModal`
     cy.visit(url)
     cy.wait(3000)
   })
diff --git a/v3/cypress/e2e/plugin.spec.ts b/v3/cypress/e2e/plugin.spec.ts
index b529851d24..d978199643 100644
--- a/v3/cypress/e2e/plugin.spec.ts
+++ b/v3/cypress/e2e/plugin.spec.ts
@@ -164,7 +164,7 @@ context("codap plugins", () => {
     graph.getInspectorPalette().find("[data-testid=adornment-checkbox-count-percent]").click()
 
     openAPITester()
-  
+
     // Get the graph tile ID.
     const cmd1 = `{
       "action": "get",
@@ -420,7 +420,7 @@ context("codap plugins", () => {
     webView.clearAPITesterResponses()
 
     cy.log("Broadcast notifications involving dragging")
-    const url = `${Cypress.config("index")}?mouseSensor`
+    const url = `${Cypress.config("index")}?mouseSensor&noEntryModal`
     cy.visit(url)
     openAPITester()
     webView.toggleAPITesterFilter()
diff --git a/v3/cypress/e2e/smoke/single-codap-smoke.ts b/v3/cypress/e2e/smoke/single-codap-smoke.ts
index b1d5f887e3..8cdd2d8651 100644
--- a/v3/cypress/e2e/smoke/single-codap-smoke.ts
+++ b/v3/cypress/e2e/smoke/single-codap-smoke.ts
@@ -12,7 +12,7 @@ import { AxisHelper as ah } from "../../support/helpers/axis-helper"
 
 context("codap single smoke test", () => {
   beforeEach(function () {
-    const url = `${Cypress.config("index")}?mouseSensor`
+    const url = `${Cypress.config("index")}?mouseSensor&noEntryModal`
     cy.visit(url)
   })
 
diff --git a/v3/cypress/e2e/tool-shelf.spec.ts b/v3/cypress/e2e/tool-shelf.spec.ts
index a73eb2891e..b0b816639a 100644
--- a/v3/cypress/e2e/tool-shelf.spec.ts
+++ b/v3/cypress/e2e/tool-shelf.spec.ts
@@ -14,7 +14,7 @@ const privacyURL = 'https://codap.concord.org/privacy'
 
 context("codap toolbar", () => {
   beforeEach(function () {
-    const url = `${Cypress.config("index")}?mouseSensor`
+    const url = `${Cypress.config("index")}?mouseSensor&noEntryModal`
     cy.visit(url)
   })
   it("will open a new table", () => {
diff --git a/v3/cypress/support/elements/cfm.ts b/v3/cypress/support/elements/cfm.ts
index 3475cf23d8..b22db9733a 100644
--- a/v3/cypress/support/elements/cfm.ts
+++ b/v3/cypress/support/elements/cfm.ts
@@ -5,6 +5,9 @@ export const CfmElements = {
   openLocalDoc(filename: string) {
     cy.get('#codap-app-id').selectFile(filename, { action: 'drag-drop' })
   },
+  openLocalDocWithUserEntry(filename: string) {
+    cy.get('#user-entry-drop-overlay').selectFile(filename, { action: 'drag-drop' })
+  },
   closeDocument(options?: IDocumentOptions) {
     this.getHamburgerMenuButton().click()
     this.getHamburgerMenu().contains("li", "Close").click()
diff --git a/v3/cypress/support/helpers/formula-helper.ts b/v3/cypress/support/helpers/formula-helper.ts
index 8f356ab04c..9cf1b0f6c6 100644
--- a/v3/cypress/support/helpers/formula-helper.ts
+++ b/v3/cypress/support/helpers/formula-helper.ts
@@ -10,7 +10,7 @@ export const FormulaHelper = {
     cy.wait(1000)
   },
   importFile(file: string) {
-    cfm.openLocalDoc(file)
+    cfm.openLocalDocWithUserEntry(file)
     cy.wait(1000)
   },
   clearFormulaInput() {
diff --git a/v3/src/components/app.scss b/v3/src/components/app.scss
index a61bae48c1..3d1a6fd614 100644
--- a/v3/src/components/app.scss
+++ b/v3/src/components/app.scss
@@ -25,3 +25,18 @@
   right: 0;
   bottom: 0;
 }
+
+#user-entry-drop-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100vw;
+  height: 100vh;
+  z-index: 5000;
+  pointer-events: auto;
+  background-color: #77777780;
+  z-index: 200;
+  &.show-highlight {
+    border: 3px solid yellow;
+  }
+}
diff --git a/v3/src/components/app.tsx b/v3/src/components/app.tsx
index a04c81ccf9..3ca4d513c9 100644
--- a/v3/src/components/app.tsx
+++ b/v3/src/components/app.tsx
@@ -1,5 +1,6 @@
+import { useDisclosure } from "@chakra-ui/react"
 import { observer } from "mobx-react-lite"
-import React, { useCallback, useEffect } from "react"
+import React, { useCallback, useEffect, useState } from "react"
 import { CfmContext } from "../hooks/use-cfm-context"
 import { DocumentContentContext } from "../hooks/use-document-content"
 import {useDropHandler} from "../hooks/use-drop-handler"
@@ -23,9 +24,10 @@ import { getSharedModelManager } from "../models/tiles/tile-environment"
 import { registerTileTypes } from "../register-tile-types"
 import { importSample, sampleData } from "../sample-data"
 import { urlParams } from "../utilities/url-params"
-import { kCodapAppElementId } from "./constants"
+import { kCodapAppElementId, kUserEntryDropOverlay } from "./constants"
 import { Container } from "./container/container"
 import { MenuBar, kMenuBarElementId } from "./menu-bar/menu-bar"
+import { UserEntryModal } from "./menu-bar/user-entry-modal"
 import { ToolShelf } from "./tool-shelf/tool-shelf"
 import { kWebViewTileType } from "./web-view/web-view-defs"
 import { isWebViewModel, IWebViewModel } from "./web-view/web-view-model"
@@ -46,10 +48,18 @@ registerTileTypes([])
 
 export const App = observer(function App() {
   useKeyStates()
+  // default behavior is to show the user enty modal when CODAP is loaded
+  // We close the modal if user imports, drags a document, opens a document
+  // or plugin using url params
+  const {isOpen: isOpenUserEntry, onOpen: onOpenUserEntry, onClose: onCloseUserEntry} = useDisclosure()
+  const [isDragOver, setIsDragOver] = useState(false)
 
-  const { cfm, cfmReadyPromise } = useCloudFileManager({
-    appOrMenuElemId: kMenuBarElementId
-  })
+  const handleFileOpened = useCallback(() => {
+    onCloseUserEntry()
+  }, [onCloseUserEntry])
+
+  const { cfm, cfmReadyPromise } = useCloudFileManager(
+    {appOrMenuElemId: kMenuBarElementId}, handleFileOpened)
 
   const handleImportDataSet = useCallback(
     function handleImportDataSet(data: IDataSet, options?: IImportDataSetOptions) {
@@ -65,22 +75,26 @@ export const App = observer(function App() {
       })
       // return to "normal" after import process is complete
       sharedData?.dataSet.completeSnapshot()
-    }, [])
+      onCloseUserEntry()
+    }, [onCloseUserEntry])
 
   const handleImportDocument = useCallback((file: File) => {
     cfm?.client.openLocalFileWithConfirmation(file)
-  }, [cfm])
+    onCloseUserEntry()
+  }, [cfm, onCloseUserEntry])
 
   const handleUrlDrop = useCallback((url: string) => {
     const tile = appState.document.content?.createOrShowTile(kWebViewTileType)
     isWebViewModel(tile?.content) && tile?.content.setUrl(url)
-  }, [])
+    onCloseUserEntry()
+  }, [onCloseUserEntry])
 
   useDropHandler({
-    selector: `#${kCodapAppElementId}`,
+    selector: isOpenUserEntry ? `#${kUserEntryDropOverlay}` : `#${kCodapAppElementId}`,
     onImportDataSet: handleImportDataSet,
     onImportDocument: handleImportDocument,
-    onHandleUrlDrop: handleUrlDrop
+    onHandleUrlDrop: handleUrlDrop,
+    onSetIsDragOver: setIsDragOver
   })
 
   useEffect(() => {
@@ -91,17 +105,21 @@ export const App = observer(function App() {
     }
 
     async function initialize() {
+      const {sample, dashboard, di, noEntryModal} = urlParams
+      const _sample = sampleData.find(name => sample === name.toLowerCase())
+      const isDashboard = dashboard !== undefined
+      const hideUserEntryModal = () => {
+        return (sample || dashboard || di || noEntryModal !== undefined)
+      }
       // create the initial sample data (if specified) or a new data set
       if (gDataBroker.dataSets.size === 0) {
-        const sample = sampleData.find(name => urlParams.sample === name.toLowerCase())
-        const isDashboard = urlParams.dashboard !== undefined
-        if (sample) {
+        if (_sample) {
           try {
-            const data = await importSample(sample)
+            const data = await importSample(_sample)
             appState.document.content?.importDataSet(data, { createDefaultTile: !isDashboard })
           }
           catch (e) {
-            console.warn(`Failed to import sample "${sample}"`)
+            console.warn(`Failed to import sample "${_sample}"`)
           }
         }
         else if (isDashboard) {
@@ -113,7 +131,6 @@ export const App = observer(function App() {
         }
       }
 
-      const { di } = urlParams
       if (typeof di === "string") {
         // wait for CFM to complete its initialization
         await cfmReadyPromise
@@ -127,6 +144,7 @@ export const App = observer(function App() {
           if (plugins.length > 0 && plugins.some(pI => pI.url === di)) {
             return
           }
+          //Do not show user entry modal
         }
         // setTimeout ensures that other components have been rendered,
         // which is necessary to properly position the plugin.
@@ -138,13 +156,18 @@ export const App = observer(function App() {
         })
       }
 
+      if (hideUserEntryModal()) {
+        onCloseUserEntry()
+      } else {
+        onOpenUserEntry()
+      }
+
       appState.enableDocumentMonitoring()
       Logger.initializeLogger(appState.document)
     }
 
     initialize()
-  }, [cfmReadyPromise])
-
+  }, [cfmReadyPromise, onCloseUserEntry, onOpenUserEntry])
   return (
     <CodapDndContext>
       <DocumentContentContext.Provider value={appState.document.content}>
@@ -154,6 +177,14 @@ export const App = observer(function App() {
             <ToolShelf document={appState.document}/>
             <Container/>
           </div>
+          {isOpenUserEntry &&
+            <div id={`${kUserEntryDropOverlay}`} className={`${isOpenUserEntry && isDragOver ? "show-highlight" : ""}`}>
+            <UserEntryModal
+              isOpen={isOpenUserEntry}
+              onClose={onCloseUserEntry}
+            />
+            </div>
+          }
         </CfmContext.Provider>
       </DocumentContentContext.Provider>
     </CodapDndContext>
diff --git a/v3/src/components/codap-modal.scss b/v3/src/components/codap-modal.scss
index 63bf0031ef..dbfb4b0676 100644
--- a/v3/src/components/codap-modal.scss
+++ b/v3/src/components/codap-modal.scss
@@ -86,7 +86,3 @@ $modal-width: 350px;
 .chakra-modal__content-container {
   position: absolute !important;
 }
-
-.chakra-modal__overlay {
-  opacity: 0.01 !important;
-}
diff --git a/v3/src/components/codap-modal.tsx b/v3/src/components/codap-modal.tsx
index dcb4c83bb1..c0e729e173 100644
--- a/v3/src/components/codap-modal.tsx
+++ b/v3/src/components/codap-modal.tsx
@@ -28,13 +28,13 @@ export const CodapModal = forwardRef(({
     >
       <ModalOverlay />
       <DraggableModalContent
-          fRef={ref}
-          modalWidth={modalWidth}
-          modalHeight={modalHeight}
-          onClick={onClick}
-          isOpen={isOpen}
-        >
-          {children}
+        fRef={ref}
+        modalWidth={modalWidth}
+        modalHeight={modalHeight}
+        onClick={onClick}
+        isOpen={isOpen}
+      >
+        {children}
       </DraggableModalContent>
     </Modal>
   )
diff --git a/v3/src/components/constants.ts b/v3/src/components/constants.ts
index b363e67ed5..3784c35a64 100644
--- a/v3/src/components/constants.ts
+++ b/v3/src/components/constants.ts
@@ -13,3 +13,4 @@ export const kDefaultTileWidth = 250
 export const kDefaultTileHeight = 250
 
 export const kCodapAppElementId = "codap-app-id"
+export const kUserEntryDropOverlay = "user-entry-drop-overlay"
diff --git a/v3/src/components/menu-bar/user-entry-modal.scss b/v3/src/components/menu-bar/user-entry-modal.scss
new file mode 100644
index 0000000000..1d8f4f73a4
--- /dev/null
+++ b/v3/src/components/menu-bar/user-entry-modal.scss
@@ -0,0 +1,58 @@
+.user-entry-modal-container {
+  font-family: "museo-sans", sans-serif;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-direction: column;
+  position: fixed;
+  height: 190px;
+  width: 440px;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  z-index: 200;
+  border: 1px solid #aaa;
+  background-color: #fff;
+  
+   &:focus {
+    outline: none;
+  }
+
+  .user-entry-modal-header {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 100%;
+    height: 30px;
+    background-color: #0b4e5d;
+    color: white;
+    font-size: 12px;
+    text-transform: uppercase;
+    padding: 10px
+  }
+
+  .user-entry-modal-body {
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    padding: 20px;
+
+    .user-entry-button {
+      background-color: #72bfca;
+      color: white;
+      font-size: 15px;
+      text-transform: uppercase;
+      margin: 10px;
+
+      &.default {
+        outline: 2px solid #0085f2;
+        outline-offset: 2px;
+      }
+      &:hover {
+        background: #3c94a1 !important;
+        color: white;
+      }
+
+    }
+  }
+}
diff --git a/v3/src/components/menu-bar/user-entry-modal.tsx b/v3/src/components/menu-bar/user-entry-modal.tsx
new file mode 100644
index 0000000000..787bb1b0e4
--- /dev/null
+++ b/v3/src/components/menu-bar/user-entry-modal.tsx
@@ -0,0 +1,69 @@
+import { Button } from "@chakra-ui/react"
+import React, { useEffect, useRef } from "react"
+import { useCfmContext } from "../../hooks/use-cfm-context"
+import { t } from "../../utilities/translation/translate"
+
+import "./user-entry-modal.scss"
+
+interface IProps {
+  isOpen: boolean
+  onClose: () => void
+}
+
+export const UserEntryModal = ({ isOpen, onClose }: IProps) => {
+  const cfm = useCfmContext()
+  const modalRef = useRef<HTMLDivElement>(null)
+
+  const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
+    if (event.key === "Escape") {
+      onClose()
+    }
+    if (event.key === "Enter") {
+      openDocument()
+    }
+  }
+
+  const openDocument = () => {
+    cfm?.client.openFileDialog()
+    onClose()
+  }
+
+  const createNewDocument = () => {
+    onClose()
+  }
+
+  const buttons = [{
+    label: t("DG.main.userEntryView.openDocument"),
+    onClick: openDocument,
+    default: true
+  }, {
+    label: t("DG.main.userEntryView.newDocument"),
+    onClick: createNewDocument
+  }]
+
+  useEffect(() => {
+    if (isOpen && modalRef.current) {
+      modalRef.current.focus()
+    }
+  }, [isOpen])
+
+  return (
+    <div ref={modalRef} tabIndex={-1} className="user-entry-modal-container" onKeyDown={handleKeyDown}>
+      <div className="user-entry-modal-header">
+        <div className="user-entry-modal-title">
+          {t("DG.main.userEntryView.title")}
+        </div>
+      </div>
+      <div className="user-entry-modal-body">
+        { buttons.map((b, idx) => (
+            <Button key={`${b.label}-${idx}`} size="md" ml="15"
+                    className={`user-entry-button ${b.default ? "default" : ""}`}
+                    onClick={b.onClick} data-testid={`${b.label}-button`}>
+              {b.label}
+            </Button>
+          ))
+        }
+      </div>
+    </div>
+  )
+}
diff --git a/v3/src/hooks/use-drop-handler.ts b/v3/src/hooks/use-drop-handler.ts
index 8d5ab92122..502ac3f0d0 100644
--- a/v3/src/hooks/use-drop-handler.ts
+++ b/v3/src/hooks/use-drop-handler.ts
@@ -7,9 +7,10 @@ export interface IDropHandler {
   onImportDataSet?: (data: IDataSet) => void
   onImportDocument?: (file: File) => void
   onHandleUrlDrop?: (url: string) => void
+  onSetIsDragOver?: (isDragOver: boolean) => void
 }
 export const useDropHandler = ({
-  selector, onImportDataSet, onImportDocument, onHandleUrlDrop
+  selector, onImportDataSet, onImportDocument, onHandleUrlDrop, onSetIsDragOver: setIsDragOver
 }: IDropHandler) => {
   const eltRef = useRef<HTMLElement | null>(null)
 
@@ -19,6 +20,7 @@ export const useDropHandler = ({
     function dragOverHandler(event: DragEvent) {
       // Prevent default behavior (Prevent file from being opened)
       event.preventDefault()
+      setIsDragOver?.(true)
     }
 
     function dropHandler(event: DragEvent) {
@@ -73,6 +75,7 @@ export const useDropHandler = ({
           event.dataTransfer.clearData()
         }
       }
+      setIsDragOver?.(false)
     }
 
     eltRef.current?.addEventListener('dragover', dragOverHandler)
@@ -82,7 +85,7 @@ export const useDropHandler = ({
       eltRef.current?.removeEventListener('dragover', dragOverHandler)
       eltRef.current?.removeEventListener('drop', dropHandler)
     }
-  }, [onHandleUrlDrop, onImportDataSet, onImportDocument, selector])
+  }, [onHandleUrlDrop, onImportDataSet, onImportDocument, selector, setIsDragOver])
 
   // return element to which listeners were attached; useful for tests
   return eltRef.current
diff --git a/v3/src/lib/cfm/use-cloud-file-manager.ts b/v3/src/lib/cfm/use-cloud-file-manager.ts
index 2ecb5a6bd2..e798c74549 100644
--- a/v3/src/lib/cfm/use-cloud-file-manager.ts
+++ b/v3/src/lib/cfm/use-cloud-file-manager.ts
@@ -141,7 +141,7 @@ function getMenuConfig(cfm: CloudFileManager) {
   ]
 }
 
-export function useCloudFileManager(optionsArg: CFMAppOptions) {
+export function useCloudFileManager(optionsArg: CFMAppOptions, onFileOpened?: () => void) {
   const options = useRef(optionsArg)
   const root = useRef<Root | undefined>()
   const cfm = useMemo(() => createCloudFileManager(), [])
@@ -252,6 +252,9 @@ export function useCloudFileManager(optionsArg: CFMAppOptions) {
 
     clientConnect(cfm, function cfmEventCallback(event: CloudFileManagerClientEvent) {
       handleCFMEvent(cfm.client, event)
+      if (event.type === "openedFile" && onFileOpened) {
+        onFileOpened()
+      }
       const cfmReadyEvents = ["fileOpened", "ready"]
       if (cfmReadyEvents.includes(event.type)) {
         cfmReadyResolver.current?.()
@@ -259,7 +262,7 @@ export function useCloudFileManager(optionsArg: CFMAppOptions) {
     })
 
     appState.setCFM(cfm)
-  }, [cfm])
+  }, [cfm, onFileOpened])
 
   return { cfm, cfmReadyPromise }
 }
diff --git a/v3/src/utilities/url-params.ts b/v3/src/utilities/url-params.ts
index 9f9fb58a74..431601cf19 100644
--- a/v3/src/utilities/url-params.ts
+++ b/v3/src/utilities/url-params.ts
@@ -93,6 +93,11 @@ export interface UrlParams {
    * value: ignored
    */
   noDataTips?: string | null
+  /*
+  * Primarily for testing -- when present does not show the user entry modal on startup.
+  * value: ignored
+  */
+  noEntryModal?: string | null
   /*
    * [v2/v3] Specifies the url of the folder from which to load plugins.
    * Useful for testing/debugging plugins from alternate locations.


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: 

Copilot reviewed 4 out of 7 changed files in this pull request and generated 1 comment.

<details>
<summary>Files not reviewed (3)</summary>

* **v3/src/components/app.scss**: Language not supported
* **v3/src/components/codap-modal.scss**: Language not supported
* **v3/src/components/menu-bar/user-entry-modal.scss**: Language not supported
</details>

<details>
<summary>Comments suppressed due to low confidence (1)</summary>

**v3/src/components/menu-bar/user-entry-modal.tsx:52**
* [nitpick] Using an empty string as the variant when 'b.default' is false may lead to unclear styling. Consider explicitly specifying a fallback variant or conditionally omitting the variant prop to improve clarity.
```
variant={`${b.default ? "default" : ""}`}
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T15:54:51Z
user: login: bfinzer
body: There are some missing V2 behaviors that appear to be implemented but don't appear to be working.
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T19:56:15Z
user: login: kswenson
body: 👍 Generally looks good -- there are still some vestiges of previous attempts which should be removed and I made a few other suggestions beyond the things that @bfinzer pointed out.
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-04-15T23:54:50Z
user: login: bfinzer
body: 👍🏻LGTM
There's a lot I don't understand in the code, but I tested the functionality and it seems to work as desired now.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T17:24:52Z
user: login: kswenson
body: 👍 Looks good -- just a few remaining tweaks.
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-17T17:44:25Z

Review comments:
diff_hunk: @@ -65,19 +71,22 @@ export const App = observer(function App() {
       })
       // return to "normal" after import process is complete
       sharedData?.dataSet.completeSnapshot()
-    }, [])
+      onClose()
+    }, [onClose])
 
   const handleImportDocument = useCallback((file: File) => {
     cfm?.client.openLocalFileWithConfirmation(file)
-  }, [cfm])
+    onClose()
+  }, [cfm, onClose])
 
   const handleUrlDrop = useCallback((url: string) => {
     const tile = appState.document.content?.createOrShowTile(kWebViewTileType)
     isWebViewModel(tile?.content) && tile?.content.setUrl(url)
-  }, [])
+    onClose()
+  }, [onClose])
 
   useDropHandler({
-    selector: `#${kCodapAppElementId}`,
+    selector: isOpen ? `#${kUserEntryDropOverlay}` : `#${kCodapAppElementId}`,
     onImportDataSet: handleImportDataSet,
     onImportDocument: handleImportDocument,
     onHandleUrlDrop: handleUrlDrop
path: v3/src/components/app.tsx
user: login: Copilot
body: The drop overlay selector condition relies solely on the modal's 'isOpen' state, which may not accurately reflect when a file is being dragged over the drop area. Consider introducing a dedicated 'dragActive' state to better control the highlight behavior during local file drag events.
```suggestion
    selector: dragActive ? `#${kUserEntryDropOverlay}` : `#${kCodapAppElementId}`,
    onImportDataSet: handleImportDataSet,
    onImportDocument: handleImportDocument,
    onHandleUrlDrop: handleUrlDrop,
    onDragEnter: () => setDragActive(true),
    onDragLeave: () => setDragActive(false),
    onDrop: () => setDragActive(false)
```
created_at: 2025-04-15T15:54:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,69 @@
+.user-entry-modal-container {
+  font-family: "museo-sans", sans-serif;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-direction: column;
+  position: fixed;
+  height: 190px;
+  width: 440px;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  z-index: 200;
+  border: 1px solid #aaa;
+  background-color: #fff;
+
+  .user-entry-modal-header {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 100%;
+    height: 30px;
+    background-color: #0b4e5d;
+    color: white;
+    font-size: 12px;
+    text-transform: uppercase;
+    padding: 10px
+  }
+
+  .user-entry-modal-body {
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    padding: 20px;
+
+    button {
+      background-color: #72bfca;
+      color: white;
+      font-size: 15px;
+      text-transform: uppercase;
+      margin: 10px;
+    }
+  }
+}
+// .user-entry-modal.codap-modal-header {
path: v3/src/components/menu-bar/user-entry-modal.scss
user: login: bfinzer
body: Remove these?
created_at: 2025-04-15T19:31:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,62 @@
+import { Button } from "@chakra-ui/react"
+import React from "react"
+import { useCfmContext } from "../../hooks/use-cfm-context"
+import { t } from "../../utilities/translation/translate"
+
+import "./user-entry-modal.scss"
+
+interface IProps {
+  isOpen: boolean
+  onClose: () => void
+}
+
+export const UserEntryModal = ({ isOpen, onClose }: IProps) => {
+  const cfm = useCfmContext()
+
+  const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
+    if (event.key === "Escape") {
+      onClose()
+    }
+    if (event.key === "Enter") {
+      openDocument()
+    }
+  }
+
+  const openDocument = () => {
+    cfm?.client.openFileDialog()
+    onClose()
+  }
+
+  const createNewDocument = () => {
+    onClose()
+  }
+
+  const buttons = [{
+    label: t("DG.main.userEntryView.openDocument"),
+    onClick: openDocument,
+    default: true
path: v3/src/components/menu-bar/user-entry-modal.tsx
user: login: bfinzer
body: The two buttons do not appear different so the `default` doesn't seem to be working.
created_at: 2025-04-15T19:44:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,62 @@
+import { Button } from "@chakra-ui/react"
+import React from "react"
+import { useCfmContext } from "../../hooks/use-cfm-context"
+import { t } from "../../utilities/translation/translate"
+
+import "./user-entry-modal.scss"
+
+interface IProps {
+  isOpen: boolean
+  onClose: () => void
+}
+
+export const UserEntryModal = ({ isOpen, onClose }: IProps) => {
+  const cfm = useCfmContext()
+
+  const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
+    if (event.key === "Escape") {
+      onClose()
+    }
+    if (event.key === "Enter") {
+      openDocument()
+    }
+  }
+
+  const openDocument = () => {
+    cfm?.client.openFileDialog()
+    onClose()
+  }
+
+  const createNewDocument = () => {
+    onClose()
+  }
+
+  const buttons = [{
+    label: t("DG.main.userEntryView.openDocument"),
+    onClick: openDocument,
+    default: true
+  }, {
+    label: t("DG.main.userEntryView.newDocument"),
+    onClick: createNewDocument
+  }]
+
+  return (
+    <div className="user-entry-modal-container" aria-modal="true" onKeyDown={handleKeyDown}>
+      <div className="user-entry-modal-header">
+        <div className="user-entry-modal-title">
+          {t("DG.main.userEntryView.title")}
+        </div>
+      </div>
+      <div className="user-entry-modal-body">
+        { buttons.map((b, idx) => (
+            <Button key={`${b.label}-${idx}`} size="md" variant={`${b.default ? "default" : ""}`} ml="15"
+                    onClick={b.onClick} _hover={{backgroundColor: "#3c94a1", color: "white"}}
path: v3/src/components/menu-bar/user-entry-modal.tsx
user: login: bfinzer
body: Is there a reason for not including the hover behavior in the css?
created_at: 2025-04-15T19:47:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -65,19 +72,23 @@ export const App = observer(function App() {
       })
       // return to "normal" after import process is complete
       sharedData?.dataSet.completeSnapshot()
-    }, [])
+      // setIsDragging(true)
path: v3/src/components/app.tsx
user: login: bfinzer
body: Remove comment?
created_at: 2025-04-15T19:48:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -50,7 +50,7 @@ interface IDraggableModalContentProps {
   isOpen: boolean
 }
 
-const DraggableModalContent = ({children, modalWidth, modalHeight, onClick, fRef, isOpen
+const DraggableModalContent = ({children, modalWidth, modalHeight, onClick, fRef, isOpen,
path: v3/src/components/codap-modal.tsx
user: login: bfinzer
body: Spurious comma at end of line?
created_at: 2025-04-15T19:54:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -46,6 +48,11 @@ registerTileTypes([])
 
 export const App = observer(function App() {
   useKeyStates()
+  // default behavior is to show the user enty modal when CODAP is loaded
+  // We close the modal if user imports, drags a document, opens a document
+  // or plugin using url params
+  const {isOpen, onOpen, onClose} = useDisclosure()
path: v3/src/components/app.tsx
user: login: kswenson
body: ```suggestion
  const {isOpen: isOpenUserEntry, onOpen: onOpenUserEntry, onClose: onCloseUserEntry} = useDisclosure()
```
Makes the code below more readable.
created_at: 2025-04-15T23:19:17Z
author_association: MEMBER
diff_hunk: @@ -91,17 +102,26 @@ export const App = observer(function App() {
     }
 
     async function initialize() {
+      const {sample, dashboard, di, noEntryModal} = urlParams
+      const _sample = sampleData.find(name => sample === name.toLowerCase())
+      const isDashboard = dashboard !== undefined
+      const hasHashFileParam = window.location.hash.startsWith("#file=examples:")
path: v3/src/components/app.tsx
user: login: kswenson
body: This isn't going to catch all of the ways that the CFM can open a file, e.g. shared documents, etc. I think a better approach would be to close it upon receipt of `openedFile` from the CFM. One way to do that would be to add an `onFileOpened` callback to `useCloudFilemanager()` which is called when the `fileOpened` event is received.
created_at: 2025-04-15T23:26:07Z
author_association: MEMBER
diff_hunk: @@ -88,5 +88,7 @@ $modal-width: 350px;
 }
 
 .chakra-modal__overlay {
-  opacity: 0.01 !important;
+  &.no-overlay {
+    opacity: 0.01 !important;
+  }
path: v3/src/components/codap-modal.scss
user: login: kswenson
body: `no-overlay` does not appear to be used any more.
created_at: 2025-04-15T23:53:17Z
author_association: MEMBER
diff_hunk: @@ -121,6 +126,7 @@ export interface UrlParams {
    * value: url string
    */
   url?: string | null
+
path: v3/src/utilities/url-params.ts
user: login: kswenson
body: ```suggestion
```
created_at: 2025-04-17T17:39:11Z
author_association: MEMBER
diff_hunk: @@ -7,9 +7,10 @@ export interface IDropHandler {
   onImportDataSet?: (data: IDataSet) => void
   onImportDocument?: (file: File) => void
   onHandleUrlDrop?: (url: string) => void
+  setIsDragOver?: (isDragOver: boolean) => void
path: v3/src/hooks/use-drop-handler.ts
user: login: kswenson
body: ```suggestion
  onSetIsDragOver?: (isDragOver: boolean) => void
```
Conventional naming for callbacks.
created_at: 2025-04-17T17:43:02Z
author_association: MEMBER
diff_hunk: @@ -65,22 +75,26 @@ export const App = observer(function App() {
       })
       // return to "normal" after import process is complete
       sharedData?.dataSet.completeSnapshot()
-    }, [])
+      onCloseUserEntry()
+    }, [onCloseUserEntry])
 
   const handleImportDocument = useCallback((file: File) => {
     cfm?.client.openLocalFileWithConfirmation(file)
-  }, [cfm])
+    onCloseUserEntry()
+  }, [cfm, onCloseUserEntry])
 
   const handleUrlDrop = useCallback((url: string) => {
     const tile = appState.document.content?.createOrShowTile(kWebViewTileType)
     isWebViewModel(tile?.content) && tile?.content.setUrl(url)
-  }, [])
+    onCloseUserEntry()
+  }, [onCloseUserEntry])
 
   useDropHandler({
-    selector: `#${kCodapAppElementId}`,
+    selector: isOpenUserEntry ? `#${kUserEntryDropOverlay}` : `#${kCodapAppElementId}`,
     onImportDataSet: handleImportDataSet,
     onImportDocument: handleImportDocument,
-    onHandleUrlDrop: handleUrlDrop
+    onHandleUrlDrop: handleUrlDrop,
+    setIsDragOver
path: v3/src/components/app.tsx
user: login: kswenson
body: ```suggestion
    onSetIsDragOver: setIsDragOver
```
Conventional naming for callbacks.
created_at: 2025-04-17T17:43:43Z
author_association: MEMBER

PR comments:
user: login: codecov[bot]
created_at: 2025-04-15T15:55:38Z
body: ## [Codecov](https://app.codecov.io/gh/concord-consortium/codap/pull/1909?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=concord-consortium) Report
Attention: Patch coverage is `86.20690%` with `8 lines` in your changes missing coverage. Please review.
> Project coverage is 87.88%. Comparing base [(`78835e0`)](https://app.codecov.io/gh/concord-consortium/codap/commit/78835e087a8a56757fd32fd718e621a72fa804e1?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=concord-consortium) to head [(`4ad3220`)](https://app.codecov.io/gh/concord-consortium/codap/commit/4ad3220d4014eb33ba515ee640834af83196db8a?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=concord-consortium).
> Report is 42 commits behind head on main.

| [Files with missing lines](https://app.codecov.io/gh/concord-consortium/codap/pull/1909?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=concord-consortium) | Patch % | Lines |
|---|---|---|
| [v3/src/components/menu-bar/user-entry-modal.tsx](https://app.codecov.io/gh/concord-consortium/codap/pull/1909?src=pr&el=tree&filepath=v3%2Fsrc%2Fcomponents%2Fmenu-bar%2Fuser-entry-modal.tsx&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=concord-consortium#diff-djMvc3JjL2NvbXBvbmVudHMvbWVudS1iYXIvdXNlci1lbnRyeS1tb2RhbC50c3g=) | 80.00% | [3 Missing and 2 partials :warning: ](https://app.codecov.io/gh/concord-consortium/codap/pull/1909?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=concord-consortium) |
| [v3/src/components/app.tsx](https://app.codecov.io/gh/concord-consortium/codap/pull/1909?src=pr&el=tree&filepath=v3%2Fsrc%2Fcomponents%2Fapp.tsx&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=concord-consortium#diff-djMvc3JjL2NvbXBvbmVudHMvYXBwLnRzeA==) | 88.88% | [3 Missing :warning: ](https://app.codecov.io/gh/concord-consortium/codap/pull/1909?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=concord-consortium) |

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff            @@
##             main    #1909    +/-   ##
========================================
  Coverage   87.88%   87.88%            
========================================
  Files         658      659     +1     
  Lines       33710    33758    +48     
  Branches     8973     9560   +587     
========================================
+ Hits        29625    29669    +44     
+ Misses       4079     3890   -189     
- Partials        6      199   +193     
```

| [Flag](https://app.codecov.io/gh/concord-consortium/codap/pull/1909/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=concord-consortium) | Coverage Δ | |
|---|---|---|
| [cypress](https://app.codecov.io/gh/concord-consortium/codap/pull/1909/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=concord-consortium) | `72.51% <85.41%> (+0.03%)` | :arrow_up: |
| [jest](https://app.codecov.io/gh/concord-consortium/codap/pull/1909/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=concord-consortium) | `56.33% <25.86%> (-0.05%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=concord-consortium#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/concord-consortium/codap/pull/1909?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=concord-consortium).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=concord-consortium).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: cypress[bot]
created_at: 2025-04-15T15:56:32Z
body: 
<h3>
  <a href="https://cloud.cypress.io/projects/msrfxa?utm_source=github&utm_medium=PASSED&utm_campaign=project">codap-v3</a>&nbsp;
  <img src="https://assets.cypress.io/github-pr-comment-icons/chevron.svg" alt="" height="14" width="14" />&nbsp;
  <a href="https://cloud.cypress.io/projects/msrfxa/runs/7073/overview?utm_source=github&utm_medium=PASSED&utm_campaign=run%20number">Run #7073</a>
</h3>

<details>
  <summary>
    <strong>Run Properties:</strong>&nbsp;
    <code><img src="https://assets.cypress.io/github-pr-comment-icons/passed.svg" alt="status check passed" height="10" width="10" />&nbsp;Passed&nbsp;<a href="https://cloud.cypress.io/projects/msrfxa/runs/7073/overview?utm_source=github&utm_medium=PASSED&utm_campaign=run%20number">#7073</a></code> &nbsp;&bull;&nbsp;
    <code><img src="https://assets.cypress.io/github-pr-comment-icons/git-commit.svg" alt="git commit" width="10" height="10" /> <a href="https://github.com/concord-consortium/codap/commit/23e590c07e2c9b86753ff7cab71f28e8e9698756">23e590c07e</a>: Increment the build number</code>
  </summary><br />

  <table width="744">
    <tbody>
      <tr />
      <tr height="48">
        <td width="224">
          <strong>Project</strong>
        </td>
        <td width="520">
          <code><a href="https://cloud.cypress.io/projects/msrfxa?utm_source=github&utm_medium=PASSED&utm_campaign=project">codap-v3</a></code>
        </td>
      </tr>
      <tr />
      <tr height="48">
        <td width="224">
          <strong>Branch Review</strong>
        </th>
        <td>
          <code><a href="https://cloud.cypress.io/projects/msrfxa/branches/main/review?utm_source=github&utm_medium=PASSED&utm_campaign=review%20changes&changeRequest=1909">main</a></code>
        </td>
      </tr>
      <tr />
      <tr height="48">
        <td><strong>Run status</strong></td>
        <td>
          <code><img src="https://assets.cypress.io/github-pr-comment-icons/passed.svg" alt="status check passed" height="10" width="10" />&nbsp;Passed&nbsp;<a href="https://cloud.cypress.io/projects/msrfxa/runs/7073/overview?utm_source=github&utm_medium=PASSED&utm_campaign=run%20number">#7073</a></code>
        </td>
      </tr>
      <tr />
      <tr height="48">
        <td><strong>Run duration</strong></td>
        <td><code>01m 45s</code></td>
      </tr>
      <tr />
      <tr height="48">
        <td><strong>Commit</strong></td>
        <td>
          <code><img src="https://assets.cypress.io/github-pr-comment-icons/git-commit.svg" alt="git commit" width="10" height="10" /> <a href="https://github.com/concord-consortium/codap/commit/23e590c07e2c9b86753ff7cab71f28e8e9698756">23e590c07e</a>: Increment the build number</code>
        </td>
      </tr>
      <tr />
      <tr height="48">
        <td><strong>Committer</strong></td>
        <td><code>eireland</code></td>
      </tr>
    </tbody>
    <tfoot>
      <tr />
      <tr height="48">
        <td colspan="2">
          <a href="https://cloud.cypress.io/projects/msrfxa/runs/7073/properties?utm_source=github&utm_medium=PASSED&utm_campaign=properties">View all properties for this run ↗︎</a>
        </td>
      </tr>
    </tfoot>
  </table>
  <hr />
</details>

<table width="744">
  <thead>
    <tr />
    <tr height="48">
      <th colspan="2" align="left" width="744">Test results</th>
    </tr>
  </thead>
  <tbody>
    <tr height="48">
      <td width="224">
        <img src="https://assets.cypress.io/github-pr-comment-icons/failed.svg" alt="Tests that failed" width="10" height="10" />&nbsp;
        <a href="https://cloud.cypress.io/projects/msrfxa/runs/7073/overview?reviewViewBy=FAILED&utm_source=github&utm_medium=PASSED&utm_campaign=failed%20tests">
          <strong>Failures</strong>
        </a>
      </td>
      <td>
        <code><strong>0</strong></code>
      </td>
    </tr>
    <tr />
    <tr height="48">
      <td width="224">
        <img src="https://assets.cypress.io/github-pr-comment-icons/flaky-v3.svg" alt="Tests that were flaky" width="10" height="10" />&nbsp;
        <a href="https://cloud.cypress.io/projects/msrfxa/runs/7073/overview?reviewViewBy=FLAKY&utm_source=github&utm_medium=PASSED&utm_campaign=flaky%20tests">
          <strong>Flaky</strong>
        </a>
      </td>
      <td>
        <code><strong>0</strong></code>
      </td>
    </tr>
    <tr />
    <tr height="48">
      <td width="224">
        <img src="https://assets.cypress.io/github-pr-comment-icons/pending.svg" alt="Tests that did not run due to a developer annotating a test with .skip" width="10" height="10" />&nbsp;
        <a href="https://cloud.cypress.io/projects/msrfxa/runs/7073/test-results?utm_source=github&utm_medium=PASSED&utm_campaign=pending%20tests&statuses=%5B%7B%22value%22%3A%22PENDING%22%2C%22label%22%3A%22PENDING%22%7D%5D">
          <strong>Pending</strong>
        </a>
      </td>
      <td>
        <code><strong>0</strong></code>
      </td>
    </tr>
    <tr />
    <tr height="48">
      <td width="224">
        <img src="https://assets.cypress.io/github-pr-comment-icons/skipped-v3.svg" alt="Tests that did not run due to a failure in a mocha hook" width="10" height="10" />&nbsp;
        <a href="https://cloud.cypress.io/projects/msrfxa/runs/7073/test-results?utm_source=github&utm_medium=PASSED&utm_campaign=skipped%20tests&statuses=%5B%7B%22value%22%3A%22SKIPPED%22%2C%22label%22%3A%22SKIPPED%22%7D%5D">
          <strong>Skipped</strong>
        </a>
      </td>
      <td>
        <code><strong>0</strong></code>
      </td>
    </tr>
    <tr />
    <tr height="48">
      <td width="224">
        <img src="https://assets.cypress.io/github-pr-comment-icons/passed.svg" alt="Tests that passed" width="10" height="10" />&nbsp;
        <a href="https://cloud.cypress.io/projects/msrfxa/runs/7073/overview?utm_source=github&utm_medium=PASSED&utm_campaign=passed%20tests">
          <strong>Passing</strong>
        </a>
      </td>
      <td>
        <code><strong>3</strong></code>
      </td>
    </tr><tfoot>
    <tr height="48"><td colspan="2"><a href="https://cloud.cypress.io/projects/msrfxa/branches/main/review?utm_source=github&utm_medium=PASSED&utm_campaign=review%20changes&changeRequest=1909">View all changes introduced in this branch ↗︎</a></td></tr></tfoot>
  </tbody>
</table>






author_association: NONE
user: login: bfinzer
created_at: 2025-04-15T20:06:57Z
body: Some enhancements needed to reach V2 functionality:
1. On initial display, <esc> should cause the dialog to disappear.
2. On initial display the "OPEN DOCUMENT …" button should be the default so that pressing <return> or <enter> should press it.
3. With the OPEN dialog box showing, when a CODAP document file is dragged into the dialog box, the document loads, but the dialog box does not automatically close.
4. Hovering over the buttons doesn't change their appearance.
author_association: CONTRIBUTOR