feat(host,client): ship the in-app directory browser as the browse package's client half

directory-picker-browse becomes dual-face: its browser half fills
ui-workspace's two directory-flow holes with the Select Workspace Directory
dialog (figma Harness 813-23126 family — Miller two-column view, breadcrumb
with click-to-edit path zone, nested New-folder dialog), driving the node
half's host.listDirectory/host.createDirectory and owning its locale
namespace (directory-browser, zh default / en). The dialog moves here from
ui-workspace wholesale — the trigger surfaces keep only the flow-hole owner
conversation.

apps/cli flips its one directory-picker row -native -> -browse, swapping the
host backend and the client interaction together; picking now works for
remote deployments out of the box. The keyless workspace-flow snapshot boots
the browse bundle and drives menu -> dialog -> Documents -> project -> Open
against the fixture tree.
This commit is contained in:
creatixchu
2026-07-28 22:22:14 +08:00
parent 05b5059dda
commit 4822622cb7
26 changed files with 1421 additions and 53 deletions

View File

@@ -40,11 +40,11 @@ const PLUGINS: readonly (WebBootEntry & { dir: string })[] = [
// Dual-face host package: its browser half fills the directory-flow holes
// (the same composition row apps/cli mounts for the node-side backend).
{
id: '@deepseek-ai/dsh-host-directory-picker-native',
dir: '../host/directory-picker-native',
url: '/plugins/directory-picker-native.js',
id: '@deepseek-ai/dsh-host-directory-picker-browse',
dir: '../host/directory-picker-browse',
url: '/plugins/directory-picker-browse.js',
rev: 'fx',
inject: ['@deepseek-ai/dsh-client-runtime', '@deepseek-ai/dsh-client-ui-workspace'],
inject: ['@deepseek-ai/dsh-client-runtime', '@deepseek-ai/dsh-client-ui-workspace', '@deepseek-ai/dsh-client-locale'],
},
]
@@ -183,7 +183,7 @@ it('locks the composer in the New Session view state until a Workspace is chosen
`)
})
it('adopts a directory through the composed native flow and lands in its blank session', async () => {
it('adopts a directory through the composed in-app browse flow and lands in its blank session', async () => {
boot('?fixture=empty')
await findLockedComposer()
@@ -194,9 +194,12 @@ it('adopts a directory through the composed native flow and lands in its blank s
expect(within(menu).getAllByRole('menuitem').map(item => visibleText(item)))
.toEqual(['Open local folder…', 'Create a new workspace'])
fireEvent.click(within(menu).getByRole('menuitem', { name: 'Open local folder…' }))
// The renderless native flow drives the fixture's deterministic pick and
// the owner adopts the returned path into a real Workspace.
await act(async () => {})
// The browse occupant renders the Select Workspace Directory dialog at the
// fixture home; select Documents, advance into project, and adopt it.
const dialog = await screen.findByRole('dialog', { name: '选择工作区目录' })
fireEvent.click(await within(dialog).findByRole('listitem', { name: /Documents/ }))
fireEvent.click(await within(dialog).findByRole('listitem', { name: /^project/ }))
fireEvent.click(within(dialog).getByRole('button', { name: '打开' }))
await findHeroComposer()
await waitFor(() => {
expect(visibleText(screen.getByRole('tree', { name: 'Sessions' }))).toContain('project')