feat(host,client): compose directory picking through slots — dual-face -native, no wire advertisement
ui-workspace's two trigger surfaces each declare a single-kind directory-flow hole (conversation.hero.workspace.directoryFlow / sidebar.workspaces.directoryFlow, same owner contract) and keep only the trigger and the adoption: the Open-local- folder entry renders while the surface's hole is occupied, and the occupant reports one picked path per open through the hole's owner conversation (open/busy/onPicked/onCancel/onError). directory-picker-native becomes dual-face: its browser half fills both holes with a renderless occupant driving host.pickDirectory, so the cordis.yml row that mounts the backend also composes the client interaction — a mismatch is impossible and a second flow package fails at client load. With composition wiring both sides, the host.describe.directoryPicker advertisement and the client's kind branching lose their last consumer: the field, WorkspacesService.directoryPickerKind(), the DirectoryPickerKind wire type, and the picker's per-open describe read are deleted. The connection fixture now serves a deterministic pickDirectory path so the keyless snapshot drives the full pick-then-adopt flow. ui-workspace's hand-rolled declaration deferral is replaced by the deferRegistration helper it duplicated.
This commit is contained in:
@@ -37,6 +37,15 @@ const PLUGINS: readonly (WebBootEntry & { dir: string })[] = [
|
||||
],
|
||||
},
|
||||
{ id: '@deepseek-ai/dsh-client-ui-trajectory', dir: 'ui-trajectory', url: '/plugins/ui-trajectory.js', rev: 'fx', inject: ['@deepseek-ai/dsh-client-ui-conversation'] },
|
||||
// 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',
|
||||
rev: 'fx',
|
||||
inject: ['@deepseek-ai/dsh-client-runtime', '@deepseek-ai/dsh-client-ui-workspace'],
|
||||
},
|
||||
]
|
||||
|
||||
const bundles = new Map(PLUGINS.map(plugin => [
|
||||
@@ -174,18 +183,24 @@ it('locks the composer in the New Session view state until a Workspace is chosen
|
||||
`)
|
||||
})
|
||||
|
||||
it('hides the Open-local-folder entry under the fixture host\'s browse picker capability', async () => {
|
||||
it('adopts a directory through the composed native flow and lands in its blank session', async () => {
|
||||
boot('?fixture=empty')
|
||||
|
||||
await findLockedComposer()
|
||||
fireEvent.click(workspaceChip())
|
||||
const menu = await screen.findByRole('menu')
|
||||
// Flush the advertised-kind read (fixture describe resolves in microtasks):
|
||||
// the fixture serves `browse`, whose in-app UI is not wired yet, so the
|
||||
// dialog affordance must not render — only the create action remains.
|
||||
await act(async () => {})
|
||||
// The composed flow package occupies the directory-flow hole, so the
|
||||
// picking affordance is present (no advertised-kind read exists anymore).
|
||||
expect(within(menu).getAllByRole('menuitem').map(item => visibleText(item)))
|
||||
.toEqual(['Create a new workspace'])
|
||||
.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 () => {})
|
||||
await findHeroComposer()
|
||||
await waitFor(() => {
|
||||
expect(visibleText(screen.getByRole('tree', { name: 'Sessions' }))).toContain('project')
|
||||
})
|
||||
})
|
||||
|
||||
it('selects the recent Workspace and opens its blank Session on first load', async () => {
|
||||
|
||||
Reference in New Issue
Block a user