The Web picker collapsed onto the directory flow (see the one-route-to-add-a-workspace
Agent Note), leaving workspace.create({ name }) with no product consumer. Delete the
whole feed line: the wire schema's name member and WorkspaceApi spelling, the gateway's
workspaceRoot config/default and the mkdir branch, the client seam that carried the name
(WorkspaceCreateInput, WorkspacesService.create, intentName), the dsh web
--workspace-root flag, and the fixture's name handling. workspace-name-conflict stays as
workspace.rename's duplicate-title error.
7.4 KiB
Agent Note: One route to add a Workspace
Status: implemented
English | 中文
Problem
两处 Workspace 表层——侧边栏区头的 + 与会话主视觉区的 chip——都提供了两条获得 Workspace 的路径:打开本地文件夹… 拉起组合的目录流程,新建工作区 接收一个名称并创建 <workspaceRoot>/<name>。两者重叠:浏览占用者自带 新建文件夹 能力,因此"选一个目录"本就覆盖了"建一个目录"。两个入口意味着同一结果有两套词汇、一个自带重名规则的名称对话框,以及一个操作者既看不到也选不了的创建位置。
删掉较弱的那个入口后,侧边栏区头只剩一个动作,于是引出了本 Note 一并裁定的展示问题:只有一行的浮层应该长什么样。
Decision
添加 Workspace 只有一条路径:通过组合的目录流程选一个宿主机目录,新建的或已存在的都可以。入口是 menu.addWorkspace("添加工作区…" / "Add workspace…");按名称创建的对话框及其 create.*/menu.createWorkspace/workspace.new 文案全部删除。标签命名的是结果而非机制,因为它现在是通往该结果的唯一一扇门——找"新建"的用户必须能找到它。
菜单的存在是为了在多个目标之间消歧。 当只剩添加这一个入口时——仅添加的侧边栏表层,或列表为空的主视觉区——锚点手势就是该动作:直接拉起流程,不渲染浮层。只有一行的浮层多花一次点击,却没有任何可选项。这条规则是一个谓词(addIsTheOnlyEntry)覆盖两处表层,而不是逐表层特判。
由该规则派生出两条边界,它们同属这个决定:
- 列表为空只有在基线落地后才算最终结果。
phase仍为pending时,主视觉区保留菜单与加载状态,而不是跳进一个即将到达的 workspace 会使其变得多余的流程。仅添加表层不列任何东西,因此从不等待。 - 目录流程的洞没有占用者时,就没有任何可添加的手段。 此时侧边栏区头干脆不渲染按钮,而不是留下一个点了没反应的按钮;主视觉区的菜单则继续作为选择器工作,列出已有内容——而当同样没有内容可列时,它什么也不弹:空浮层会宣称一个并不存在的选择。这是 seam 文档化的无流程默认行为走到它的结论:占用者不在,唯一的创建能力也就不在。主视觉区的锚点 chip 归 ui-conversation 所有,因此本包能压掉浮层,却无法隐藏该 chip。
直接拉起的这条路径同样遵守菜单项声明的 busy 规则:某次选取仍在接纳中(flowBusy)时,锚点手势会被按住,正如该菜单项会被禁用,从而使迟到的结果无法抢开第二个流程。
WorkspaceCreateFlow 现更名为 WorkspacePickFlow,其 createOnly prop 更名为 addOnly;注入的 createWorkspace 从 { name } | { path } 收窄为 { path }。
Wire and CLI residue
本节曾划定的后续删除已经落地:workspace.create 只接受 { path }(name 成员已从 wire schema 与 WorkspaceApi 移除),网关失去了 workspaceRoot 配置及其默认值,客户端 seam 收窄为 path 写法(WorkspaceCreateInput、WorkspacesService.create、intentName),dsh web --workspace-root flag 连同其 apps/cli reference 文档行一并删除。workspace-name-conflict 仍留在 wire 上,作为 workspace.rename 的重名错误。
Testing
connectFreshWorkspace——所有 web e2e 场景启动时都会走的辅助函数——会预先备好 <root>/workspace,再经对话框的路径编辑器接纳它,因此产出的会话 cwd 与按名称创建时完全一致,场景 golden 保持有效。选择预先备好而不是在对话框内新建,是为了让该辅助函数在一个场景可能发生的多次连接之间保持幂等(第二次创建同名文件夹会失败,而创建对话框会在失败时把流程停在原地)。在选择器内新建文件夹——同一条路径的另一半——由 workspace-management.e2e.ts 覆盖,它承担针对性覆盖:在对话框自己创建的文件夹上添加两个 workspace、接纳 basename 相同的不同目录并保持彼此独立、在另一个目录上复用已删除的标题、以及浏览对话框的 aria golden。
smoke-real.e2e.ts 是唯一启动未打补丁的出厂配置树的场景,其中 -auto 行会按宿主机解析;它现在通过 --config overlay 钉死 -browse,使开发机的显示环境无法决定选择器是否可被驱动。
Alternatives considered
保留 打开本地文件夹… 作为标签。 否决:合并后该入口既能打开也能创建,用机制命名会恰好对那些入口被我们删掉的用户隐藏创建这一半。反方理由——"本地"二字有效区分了浏览器所在机器与 harness 所在机器——在下一步就由对话框自身的标题和面包屑回答了。
保留双入口菜单,让 新建工作区 也打开同一个流程。 否决:同一动作两个标签正是我们被要求消除的混淆,而不是它的缩小版。
为了与主视觉区菜单保持一致而保留只有一行的浮层。 否决:不提供选择的浮层是一次浪费的点击,读起来像半成品。这里要一致的是规则(有菜单 ⇔ 存在选择),不是控件。
为将来可能新增的入口(克隆仓库、远程目录)保留菜单壳。 否决,依据"require a current owner and need":这样的入口目前并不存在,而等它到来时再恢复菜单,比现在就发一个空壳的改动更小。
在同一改动中删除 wire 的按名称创建分支。 本 PR 否决:那是 backend/CLI 面,reviewer 不同、测试波及面更广,而紧急的决定是 UI。见 residue 一节——它是被标记了,不是被遗忘了。
在 e2e scaffold 中经 host 注册 workspace,而不驱动对话框。 否决:那会让全部 15 个场景与选择器解耦,整条 lane 将无法证明幸存的这条路径能走到可用的 composer。现在每个场景都会走真实对话框来接纳自己的目录;只有"新建文件夹"那一半集中在一个场景里,因为处处重复只会让共享辅助函数失去幂等性,却换不来额外信号。
Consequences
- 从 UI 已无法在操作者选定目录之外创建 Workspace;服务端控制的
--workspace-root目标曾是约束新 workspace 文件夹落点的唯一手段,现在没有替代品。需要该约束的部署必须有意识地重新引入它。 - 仅存的这条路径会浏览宿主机文件系统,因此选择器的可达范围现在是整台宿主机,而非一个配置好的父目录。这本就是浏览占用者的契约,本改动使它成为唯一的契约。
- 挂载了
ui-workspace但未挂任何 directory-picker 包的组合,已完全无法添加 Workspace;现在它通过不渲染按钮来说明这一点,而不是提供一个按名称创建的兜底。 - 主视觉区的 chip 仍声明
aria-haspopup="menu",而直接拉起的路径实际弹出的是对话框。要让它如实,需要把流程的展示方式经conversation.hero.workspace的 owner 契约上报——决定权在流程,播报权在 chip,两者分属不同的包——因此这被列为一项具名的后续工作,而不是一处无声的不一致。本次新增的侧边栏按钮完全不作任何 popup 声明。