fix(host): the native flow discards chooser settlements after unmount

An HMR replacement left the old instance's pick promise able to adopt a
second path through the shared owner callbacks; settlements now check a
component-lifetime ref (an injected-face identity change alone keeps the
pending settlement — the host-side dialog is unchanged, and the wire has no
per-request abort). Both settlement arms covered (ds-review-bot).
This commit is contained in:
creatixchu
2026-07-29 02:24:27 +08:00
parent e7c9877ac5
commit 3f23347d10
2 changed files with 41 additions and 2 deletions

View File

@@ -35,6 +35,15 @@ export function NativeDirectoryFlow(props: DirectoryFlowOwnerProps & NativeFlowI
// latest handlers, not the ones captured when the chooser opened.
const outcome = useRef(props)
outcome.current = props
// Unmount (HMR replacing the occupant) discards settlements wholesale: the
// dead instance must neither adopt a path nor drive the owner's error
// surface. The wire carries no per-request abort, so the host-side chooser
// survives until answered — its answer just lands nowhere; the replacement
// instance re-arms under the owner's still-open request. An injected-face
// identity change alone (re-registration) keeps the pending settlement:
// the chooser on the host display is still the same dialog.
const alive = useRef(true)
useEffect(() => () => { alive.current = false }, [])
useEffect(() => {
if (!open) {
armed.current = false
@@ -43,8 +52,14 @@ export function NativeDirectoryFlow(props: DirectoryFlowOwnerProps & NativeFlowI
if (armed.current) return
armed.current = true
pick().then(
(path) => { if (path === null) outcome.current.onCancel(); else outcome.current.onPicked(path) },
(reason: unknown) => { outcome.current.onError(reason instanceof Error ? reason.message : String(reason)) },
(path) => {
if (!alive.current) return
if (path === null) outcome.current.onCancel(); else outcome.current.onPicked(path)
},
(reason: unknown) => {
if (!alive.current) return
outcome.current.onError(reason instanceof Error ? reason.message : String(reason))
},
)
}, [open, pick])
return null