Files
deepseek-harness/packages/host/directory-picker-browse/src/client/flow.ts
creatixchu 53240f4664 fix(host,client): abort superseded listings on the wire; keep the native swap resolvable
Supersession (newer navigation, path editing, closing, unmount) now
aborts the in-flight listing's request instead of only discarding its
result: the browser mints an AbortController per listing, the signal
rides the workspace face (IWorkspaces.listDirectory gains an optional
signal) onto the fetch carrier, and the Host scan stops with it (817's
cancellation chain). apps/cli keeps both picker packages as dependencies
so the documented one-row cordis.yml swap to the native backend resolves
at boot.
2026-07-29 06:19:18 +08:00

44 lines
1.9 KiB
TypeScript

/**
* The browse picking occupant (package-internal; the `./client` surface
* exposes only the Loader exports). Same-package tests exercise it directly
* through this module.
*/
import { createElement } from 'react'
import type { ReactElement } from 'react'
import type { DirectoryListing } from '@deepseek-ai/dsh-client-runtime/client'
import type { Translate } from '@deepseek-ai/dsh-client-locale/client'
// Type-only: the owner contract of the directory-flow holes.
import type { DirectoryFlowOwnerProps } from '@deepseek-ai/dsh-client-ui-workspace/client'
import { DirectoryBrowser } from './DirectoryBrowser.tsx'
/** Injected face: the browse wire calls and copy the dialog drives (bound in apply's closure). */
export interface BrowseFlowInjected {
/** List one directory level (absent path = the Host home directory); the signal aborts a superseded scan. */
listDirectory: (path?: string, signal?: AbortSignal) => Promise<DirectoryListing>
/** Create one child directory under an existing parent. */
createDirectory: (path: string, name: string) => Promise<string>
/** Localized dialog copy (this package's namespace). */
t: Translate
}
/**
* Flow occupant: adapts the hole's owner conversation onto the browser
* dialog — a confirmed directory is the picked path, dismissal is the
* cancellation. Browse failures (unreadable targets, create conflicts) stay
* inside the dialog's own alert surfaces, so the owner's `onError` arm is
* never driven by this occupant.
* @param props - owner conversation plus the injected browse face.
* @returns the dialog element (renders nothing while closed).
*/
export function BrowseDirectoryFlow(props: DirectoryFlowOwnerProps & BrowseFlowInjected): ReactElement {
return createElement(DirectoryBrowser, {
open: props.open,
busy: props.busy,
listDirectory: props.listDirectory,
createDirectory: props.createDirectory,
t: props.t,
onOpen: props.onPicked,
onClose: props.onCancel,
})
}