Просмотр исходного кода

fix(host): restart home on early-edit cancel; clamp the dialog to short viewports

Escape canceling a path edit opened before any level listed relaunches
the home listing instead of stranding a blank picker (the editor had
superseded the initial request while parent was still null). The card's
height clamps to the viewport (min(420px, 100dvh - 32px)); header and
footer are flex-none and the columns scroll, so Open/Cancel stay
reachable on landscape phones and short embedded windows.
creatixchu 2 месяцев назад
Родитель
Сommit
1a9b84b19e

+ 2 - 2
packages/client/connection/tests/fixture.spec.ts

@@ -320,13 +320,13 @@ describe('createFixtureApi', () => {
     const created = await api.host.createDirectory(req({ path: '/', name: 'srv' }))
     if (!created.result.ok) throw new Error('create failed')
     expect(created.result.value.path).toBe('/srv')
-    const listed = await api.host.listDirectory(req({ path: '/srv' }))
+    const listed = await api.host.listDirectory(req({ path: '/srv' }), new AbortController().signal)
     if (!listed.result.ok) throw new Error('list failed')
     expect(listed.result.value.crumbs).toEqual([
       { name: '/', path: '/', hidden: false },
       { name: 'srv', path: '/srv', hidden: false },
     ])
-    const root = await api.host.listDirectory(req({ path: '/' }))
+    const root = await api.host.listDirectory(req({ path: '/' }), new AbortController().signal)
     if (!root.result.ok) throw new Error('root list failed')
     expect(root.result.value.entries).toContainEqual({ name: 'srv', path: '/srv', hidden: false })
   })

+ 5 - 2
packages/host/directory-picker-browse/src/client/DirectoryBrowser.module.css

@@ -1,12 +1,15 @@
 /* Directory-browser dialog (figma 813-23126 family). The shared Modal renders
  * headless here — mask, card, Escape only — and this module owns the figma
- * frame exactly: fixed 600×420 card, header (title + crumbs, l3 separator),
+ * frame: 600×420 card (viewport-clamped), header (title + crumbs, l3 separator),
  * the one-or-two-column Miller content, and the bordered footer. */
 
 /* Doubled class beats Modal's own .dialog regardless of stylesheet order. */
+/* Short viewports clamp the card: header/footer are flex-none and the
+ * columns scroll, so shrinking the height keeps Open/Cancel reachable
+ * instead of clipping them below a fixed overlay. */
 .dialog.dialog {
   width: min(600px, 100%);
-  height: 420px;
+  height: min(420px, calc(100dvh - 32px));
   padding: 0;
   gap: 0;
 }

+ 6 - 1
packages/host/directory-picker-browse/src/client/DirectoryBrowser.tsx

@@ -1,6 +1,7 @@
 /**
  * The in-app workspace-directory browser (figma Harness 813-23126 family): a
- * fixed 600×420 dialog whose header carries the title, the selection-path
+ * 600×420 dialog (clamped to short/narrow viewports — the Miller row scrolls
+ * sideways, the columns scroll down) whose header carries the title, the selection-path
  * breadcrumb, and a click-to-edit path zone; below it a Miller view — one
  * full-width level until a row is selected, then two 256px columns (level |
  * selected folder's children) around a hairline divider. Selecting in the
@@ -364,6 +365,10 @@ export function DirectoryBrowser({ open, listDirectory, createDirectory, onOpen,
                     // half-empty two-pane view, so cancel falls back to the
                     // single-pane level.
                     if (child === null) setSelected(null)
+                    // With no level listed yet (the editor superseded the
+                    // initial home listing), plain cancellation would leave a
+                    // permanently blank picker: restart the home listing.
+                    if (parent === null) navigate()
                   }
                 }}
               />

+ 21 - 0
packages/host/directory-picker-browse/tests/directory-browser.spec.tsx

@@ -177,6 +177,27 @@ describe('DirectoryBrowser', () => {
     expect(screen.queryByLabelText('browser.editPath', { selector: 'input' })).toBeNull()
   })
 
+  it('restarts the home listing when Escape cancels an edit opened before any level listed', async () => {
+    // The initial home listing hangs; Edit Path supersedes it while parent
+    // is still null, and Escape must not strand a blank picker.
+    let settled = false
+    const gate = new Promise<never>(() => {})
+    const listDirectory = vi.fn(async (path?: string) => {
+      if (!settled) { settled = true; return gate }
+      return listingFor(path)
+    })
+    mount({ listDirectory })
+    fireEvent.click(screen.getByRole('button', { name: 'browser.editPath' }))
+    const input = screen.getByLabelText<HTMLInputElement>('browser.editPath')
+    expect(input.value).toBe('')
+    fireEvent.keyDown(input, { key: 'Escape' })
+    // Cancellation relaunched the home listing instead of leaving neither
+    // rows nor status behind.
+    await waitFor(() => { expect(screen.getByRole('listitem').textContent).toBe('Documents') })
+    expect(listDirectory).toHaveBeenCalledTimes(2)
+    expect(listDirectory).toHaveBeenLastCalledWith(undefined)
+  })
+
   it('surfaces an unreadable target as an alert and keeps the edit open for correction', async () => {
     mount()
     await waitFor(() => { expect(screen.getByRole('listitem')).toBeTruthy() })