fix(web): address the review round on the one-route add flow

- lifecycle-chrome's second scaffold staged its workspace under the OUTER
  scaffold's temp root, coupling two supposedly independent worlds and
  leaving the aria scrub root wrong; it now uses its own workspaceCwd.
- The direct-open path now carries the same `flowBusy` gate that disables
  the equivalent menu entry, so an occupant re-registering mid-adoption
  cannot raise a second flow.
- A composition with no directory-picker no longer opens a zero-entry
  popover on the hero anchor: with nothing to pick and nothing to add,
  the gesture shows nothing. Both behaviors gain a unit test.
- Brought three partially superseded Agent Notes current (the native
  picker, the workspace UI product flow, and the sidebar browsing split),
  cross-linked to this decision, both languages re-recorded.
- Corrected this Note's own Testing section: the shared e2e helper stages
  and adopts its directory, it does not create one in-dialog — only
  workspace-management does. Named the client-seam and CLI-README residue
  in the follow-up TODO alongside the wire branch.
This commit is contained in:
creatixchu
2026-07-31 16:23:49 +08:00
parent d6231007af
commit 6184ba35ca
16 changed files with 83 additions and 35 deletions

View File

@@ -114,6 +114,10 @@ export function WorkspacePickFlow({
disabled: flowBusy,
}))
: addEntries
// Nothing listed and nothing to add with (a composition that mounts this
// package without any directory-picker): an empty popover would claim a
// choice that does not exist, so the anchor gesture shows nothing at all.
const menuIsEmpty = items.length === 0
const closeModal = (): void => {
setErrorOpen(false)
@@ -153,9 +157,11 @@ export function WorkspacePickFlow({
// made unnecessary; the add-only surface lists nothing and never waits.
const listSettled = addOnly || workspaceSnapshot.phase === 'ready'
const addIsTheOnlyEntry = !pinAdd && listSettled && addEntries.length === 1
// `flowBusy` gates this exactly as it disables the equivalent menu entry: a
// pick still being adopted owns the surface until it settles.
useEffect(() => {
if (open && addIsTheOnlyEntry) openDirectoryFlow()
}, [open, addIsTheOnlyEntry, openDirectoryFlow])
if (open && addIsTheOnlyEntry && !flowBusy) openDirectoryFlow()
}, [open, addIsTheOnlyEntry, flowBusy, openDirectoryFlow])
/** Owner side of the flow conversation: adopt keeps the flow open (busy) until the Host answers. */
const flowOwner: DirectoryFlowOwnerProps = {
@@ -185,7 +191,7 @@ export function WorkspacePickFlow({
return (
<>
<Menu
open={open && !addIsTheOnlyEntry}
open={open && !addIsTheOnlyEntry && !menuIsEmpty}
anchor={null}
items={items}
{...pinAdd ? { footer: addEntries } : {}}
@@ -196,7 +202,7 @@ export function WorkspacePickFlow({
portal
getAnchorRect={getAnchorRect}
/>
{open && !addIsTheOnlyEntry && workspaceSnapshot.phase === 'pending' && <div className={css.menuStatus} role="status">{t('picker.loading')}</div>}
{open && !addIsTheOnlyEntry && !menuIsEmpty && workspaceSnapshot.phase === 'pending' && <div className={css.menuStatus} role="status">{t('picker.loading')}</div>}
{renderDirectoryFlow(flowOwner)}
<Modal
open={errorOpen}