Files
ycode/docs/加号菜单-REA核对-20261016.md

17 KiB
Raw Permalink Blame History

当前 cr 加号菜单:REA 源码核对

2026-10-16;WorkBuddy 5.8.2。只读已保存 REA JS/CSS,未操作原版 UI、未修改生产代码。本报告区分源码控制流、实际可恢复组件及仍须原版运行时确认的行为,不将 ARIA/函数存在等同于键盘全状态已验收。

Evidence、SHA 与范围

JS 来自 .tmp/rea-runtime-scripts.json,Evidence ev_717fa5e846e64d67ac19c8925d121bff632c0d3aefb3603b10858559cabb2dcb;同版本后续 .tmp/rea-expert-runtime-scripts-20261016.json Evidence ev_e91787b1d4dea651582c6cf12827775b9068b0ee8b80c68ba08d4c1d87d94e90。原 ASAR root SHA 035254bc76310ea46317433e4840602094543ef323d2d5c5aff7c131729de918。本轮重算所用 JS SHA,并用 Babel AST 定位具名函数。

简称 renderer/assets 原文件 SHA-256
I src-DbE1_mgM.js 6c6ce63c93854783270e855572797d75e536e3b8d4548410e31fe9d147e1feb4
C contexts-BMeWSUHk.js 5f35b7489b107e21ef452c8ee88a95265756f6fc444841eef869228bbc44768b
H home-CC-czDJL.js 121d5a07acccb09e48f20f3a94f9fac8b10770e4a82bca1916d2cccd333f8d14
L wb-input-add-B045GnIg.js 7842fa4321d5351bf6c822bbd0253e0d6d6e68cc9cfae3f2470b3d493c23daf4

CSS 来自 .tmp/rea-css-recovered/manifest.json,Evidence ev_377c8790468b3c67dad8577aabb327f98d3140659f0fffdbabfcff5a40cc9e94,本轮逐 SHA 核对:

  • src-CyXHA0IC.css:a5e26b537bb50323aefbf25abddad52e288dd3868598be8d255d70886fe77cfe。
  • desktop-sdk-setup-Bh0rg_xP.css:b2ee58c0caf560eeddc4eb8c91d86cabddbec634b8d37ed5d6b85d0644c0e142。

函数 offset 均为导出 JS 的 UTF-16 code units,start 包含/end 不包含。

当前组件图与状态

flowchart TD
  A[InputToolbarImpl] --> B[AddMenuImpl / memo AddMenu]
  B --> C[useAddMenuItems: provider条件与顺序]
  B --> D[Popover: 点击主入口]
  D --> E[PopoverLayer: fixed定位/portal/外点击/退场]
  E --> F[Menu + MenuItem + MenuDivider]
  F --> G[SubMenu: hover计时/焦点/嵌套锁]
  G --> H[Files / ReferenceFiles / Mode]
  G --> I[Experts / Skills / Connectors]
  H --> J[自身host实际能力回调]
  I --> J

I.InputToolbarImpl [1704717,1709947) 在 toolbar 左侧第一个位置渲染 memo 后的 hU(AddMenuImpl),此处直接 <hU />,没有向 AddMenu 传额外 disabled/onAction。主组件 [1601493,1605376) 有三项 state:isOpen、activeSubmenuId、trigger tooltip open;各项来自 input providers。

  • 主入口 click toggle Popover。打开时关 tooltip,调用 skill.onAddMenuOpen,触发 expert.loadRecentExperts(错误吞掉);关闭时将 activeSubmenuId 清空并调用可选 onClose。
  • 主菜单为 controlled Popover(open=isOpen);同一时刻仅 activeSubmenuId 对应的受控 SubMenu open。进入另一项将 ID 换成新项;旧项迟到 close 回调只有 ID 仍匹配时才清,不会把新项一并清掉。
  • 任意 item 若没有可见 children,handleItemClick 调用 onAction 并关闭;有 children 则主项 click 不执行子项动作,展开主要靠 SubMenu hover/touch/key handler。
  • connector 子菜单首次展开还调用 connector.onEntryOpen('add_menu') 与 onAddMenuOpen;这里 map 回调中的局部 C 是“该项为connectors”的布尔值,不要误读成外层主菜单 isOpen。同一 provider 内有自己的 deferred catalog 加载 guard。
  • tooltip 默认 200ms hover;主菜单已开时 disableInteractions,并以受控状态保持关闭。默认 trigger ghost/medium/circle/iconOnly;aria-label 为 chatInput.addMenu.title,打开时加 open class。

入口顺序、显隐和分隔

I.useAddMenuItems [1599779,1601311) 严格按下表顺序追加,没有另一个固定“始终显示五项”的数组:

顺序 ID 进入主菜单条件
1 add-files attachment 存在,且任一 pick handler / extraItems 非空 / process 存在
2 reference-files referenceFiles.onSelect 存在且 items.length > 0
3 mode mode provider 存在且 presentation 为 add-menu
4 experts expert provider 非 null(最近列表可空)
5 skills skill provider 存在(列表可空)
6 connectors connector.onSummonMore 存在(列表可空)

主菜单最后一个 files/reference-files 项与后面的非文件项之间插一个 divider;files 与 reference-files 两项之间不插这一分隔。空 skills/connectors 用隐藏 placeholder 保持入口,AddMenu 按 provider 特判仍渲染各自空态子菜单。所有项不可用时 AddMenu 返回 null。

因此未选工作目录时原首页可以只出现 files/mode/experts/skills/connectors;自身已有 cwd 且引用 provider 得到可用结果时出现 reference-files 符合源码条件。仅“选了 workspace”不保证有该项,还要求 provider enabled、结果非空和插入 handler。

定位、跨缝与浮层生命周期

I.Popover [384214,385129)、PopoverTrigger [385129,385791)、PopoverContent [385791,386466)、PopoverLayer [381695,383678):

  • AddMenu 主层 placement:top-start、offset 8、strategy fixed;offset→flip(padding8)→shift(padding8)→size(padding8)。size 将 availableHeight-10 与 maxHeight320 比较,下限160px。
  • 默认 trackReferenceMove=true,使用 autoUpdate(animationFrame:true),不是仅窗口 resize 时算位置。anchor 为点击按钮 ref。
  • FloatingPortal 将实际浮层从 toolbar DOM 中分离;显式带 cr-theme、data-cr-theme/data-theme,不能依赖原 toolbar 的继承主题仍自动存在。
  • useFloatingLayer [372662,373108) 按全局 group stack 分配 z-index:默认 base9990、step10;子层同样登记,不是随意写固定 z-index。
  • 主层 retained mount 用 150ms opacity transition。关闭先走可见性/transition,再 onExited 清 layerMounted;AddMenu 不直接零延迟卸掉主层。

I.SubMenu [393807,398417) 是关键复用组件:

  • 通用默认 openDelay80/closeDelay150;AddMenu 显式覆盖 openDelay0/closeDelay120。
  • placement right-start、offset4、fixed;flip 只指定 fallback left-start;shift padding8/crossAxis:true。size 限宽 [160,480],高 clamp 到默认 max480,默认最低120;available size 更小时按源码 clamp 处理,不能自行换 legacy200下限。
  • 主 trigger pointerenter 取消 close timer 并打开;pointerleave 取消 open timer,若 open 且没有 lock/输入焦点才计划 close;进入 floating 取消 close timer;离开 floating 同样计划关闭。
  • 此 cr SubMenu 不调用 safePolygon。跨4px缝的容错靠120ms close timer;不能把常见 safePolygon 行为当已证实现。
  • pa context 的 lock() 对父子嵌套子菜单计数;子菜单展开时锁住父层,归零才重新尝试 scheduleClose。
  • 如果 activeElement 是文本输入、textarea 或受支持 contenteditable,且位于 trigger/floating 内,不因 pointerleave 计划关闭。select/button 等并不在该输入匹配表中。
  • touch pointerdown preventDefault、打开并设 touch latch;touch 模式不因 pointerleave 关闭;后续 mouse pointermove 重置 latch。
  • 默认 destroyOnClose=true;主菜单关闭卸掉 SubMenu 会清 timer。destroyOnClose=false 才 retained DOM/display:none;AddMenu 没启用该分支。

外点击、Escape 与键盘:避免误抄 ContextMenu

父 Popover.useDismiss 在 outsidePress 中明确排除 [data-cr-submenu-overlay=true] 和 .cr-sub-menu__overlay。SubMenu 同时输出新旧两个 data 属性 data-cr-sub-menu-overlay / data-cr-submenu-overlay,它自己的 outsidePress 也排除任意这些 submenu overlay。缺少标记会造成点击子菜单被父层当外部点击关闭。

PopoverLayer 还有 capture pointerdown 分支处理存在 [data-floating-ui-inert] 的情况:点击其自身、anchor、submenu overlay 或更高 stacking z-index 不关闭;其它目标才 requestClose。这属于主层/其它浮层协作,不是简单 document.click。

SubMenu useDismiss 明确 referencePress:false,ancestorScroll:true,escapeKey:true;祖先滚动会关 child。父 Popover 的普通 useDismiss 没启用 ancestorScroll,主层可继续跟踪位置。一般外部 pointerdown 最终关父层和子层。输入法 composition 下通用 useDismiss 有 Escape 抑制,但下面组件级 handler 不能据此声称全部 IME 键盘路径一致。

位置 源码处理
主按钮 原生 button 支持 click/Enter/Space;PopoverTrigger clone 点击事件并 toggle,role combobox/aria-expanded/listbox。
父 Menu I.Menu [386573,386815) 仅布局/role。MenuItem [393251,393766) tabIndex=-1;没有父菜单 focus-first 或 Up/Down roving。
SubMenu trigger ArrowRight/Enter/Space preventDefault+stopPropagation,打开并 queueMicrotask 尝试 focus 首个未禁用 role=menuitem。
SubMenu overlay ArrowLeft/Escape 关当前 child、尝试 focus 父 row;Up/Down/Home/End 轮转 query 得到的 menuitem。没有通用 Enter/Space click 分支。
SearchInput [399641,400205) 非空 Escape 先清词并 stopPropagation;空输入 Escape 才到 SubMenu/useDismiss。技能/连接器默认autoFocus=true,专家显式false。
自定义子行 SkillListItem 有 tabIndex 和 Enter/Space;files/experts/connectors 的部分 role=menuitem div 没有 tabIndex,通用 .focus() 尝试不等于实际一定可聚焦。

旁边 ContextMenuPanel 有完整主列表 focus-first/Enter/Space/上下键,AddMenu 不使用它。恢复时不能把那套当成已验证 cr 加号键盘实现。根将采实际 REA 场景;本轮仅明确代码分支,未声称上表所有焦点动作已产生实际效果。

子菜单动作与实际 host 契约

files

I.FilesSubmenu [1581651,1583408) 过滤当前 provider 存在的 handler,固定原顺序:local-files、space-library、wecom-wedrive、netdrive(own组),然后 tencent-docs、ima、lexiang(third-party组);两组均非空时有 divider,随后追加 extraItems。handler 映射常量 [1583472,...] 分别为 onPickLocalFiles/onPickSpaceLibrary/onPickWecomWedrive/onPickNetDrive/onPickTencentDocs/onPickIma/onPickTencentLexiang。点击调用 handler,立即 onCloseMenu;不是在菜单里伪造已选文件。

C.useWbAttachmentProvider [769231,772634) 默认本地路径建立 input type=file,multiple=true;有 onPickLocalFilesOverride 则调用 override。选文件走 process/附件 block;有 store 时先 processing,再 completed/failed,数量上限50(扣已存在附件),并按 transport/文件类型处理大小和上传。各云 handler 来自 attachmentOverrides/宿主真实 picker;不存在就不出现,不可用本地 mock 成功冒充云端选取。

reference-files

I.ReferenceFilesSubmenu [1585923,1587169) 展示 provider.items 的文件/目录图标、base/ext、路径 tooltip,点击 provider.onSelect(item) 后关菜单。没有 openPath 或自动读全文调用。

C.useWbReferenceFilesProvider [4599343,4600657):enabled、cwd、host.searchFiles 可用且未指定外部items,才请求 {cwd,options:{search:'',resultNum:100}};按 id 去重。另读该 cwd 下 .workbuddy/applications.yaml,把有效 wbapp_ locator 应用合并。provider 最终还要求结果非空、onInsertBlocks 存在。

createReferenceFileBlock [4592574,4592870) 返回 resource_link,含 file/folder mention、filePath/path/relativePath/fileName、displayAsPhrase:true/displayAsContext:false;保存为 block 的应用项使用 application:// URI。对 YCode 的最小适配是自身合法 cwd 文件搜索和真实上下文引用插入;应用 locator/云应用目录未实现时不能造一个“已打开应用”。

mode

I.resolveModeOptions [1563614,1563708):provider.options 优先;add-menu 默认 craft(default)/plan/ask;toolbar 默认 craft/plan/quick,不能混用。ModeSubmenu [1583710,1584870) 只渲染非默认模式的 toggle。当前模式来自 draft.selections.mode,缺省默认项;点击已选 plan/ask 回 craft,点击另一个选另一个;先 input.api.setMode({id,label}) 再 provider.onChange。不关闭菜单。description 跟随当前模式;provider.disabled 或无 onChange 时禁用;带提示的 ModeToggleRow tooltip 延迟1000ms。

H 首页 onModeChange [95414,...] 接受 craft/plan/ask/quick;plan/ask 清输入 expert、app expert 和依赖 gate。C 会话 useWbModeProvider [6087096,6089698) 调实际 config.setMode,并维护权限/quick模型快照、忙/已发首条后的模式约束和错误 toast。宿主需接已有真实模式机制,不能仅让 toggle 视觉改变而消息仍按原模式执行。

experts / skills

I.ExpertsSubmenu [1574199,1579192) 使用新 provider search/recents/pins;选择关闭主菜单,再走 provider.onChange,失败恢复旧 expert。详细 summon/default/owner/generation 规则见 专家使用逻辑核对。

I.SkillsSubmenu [1594691,1599386) 点技能构造实际 skill block 插入当前 draft,记录 recent,并调用 provider.onSelect 后关闭;onBeforeSelect/prepareSelection 异步有 alive/generation/session-context guard,取消不插入。置顶/secondary action stopPropagation;新增/管理调用宿主后关菜单。不能把来源为插件的所有内部技能无差别塞入全局技能菜单;provider 的 installed 查询排除 plugin skills。

connectors

I.ConnectorsSubmenu [1568795,1573334) 普通行用于连接与能力选择,不插 resource_link/文字、不打开详情:未曾连接则 onConnectItem;未曾连接但正在 connecting 且可取消则 onCancelConnect;已连接并非 repairing 则 onToggleItem(item,!isInputConnectorOn)。普通 connect/toggle/repair 不关菜单;管理 footer 调 onSummonMore 后关闭。

C.useWbConnectorProvider [828180,847538) 的具体 delegates:

  • Xe/onConnectItem [842292,843874) 调 connectConnector;成功经 He 将 enabled selection 写给 owner,更新连接状态并 toast;needsAuth/connecting 保持 pending,而非假成功。
  • He [840080,840405) 维护 selected IDs Set,映射为含 enabled 的 items 调 onSelectionChange;不是上下文文件插入。
  • $e/onToggleItem [845424,846209) 的 selectionOnlyToggle 分支仅改变该会话能力选择;普通分支关闭时调用 disconnectMcpConnector,失败恢复选择并报错。不能把所有 toggle 都按全局卸载做。
  • Qe/onRepairItem [844689,845423) 调 repairConnector;et/onCancelConnect [846210,846947) 调 cancelMcpConnector;各自 opId guard 防旧结果落地。
  • nt/onSummonMore [846966,847083) 优先外部管理回调,否则 goto('/connectors')。

YCode 自定义 MCP 已有真实连接/逐工具能力时可映射这些宿主动作;QQ/微信/云连接器目录/授权服务缺失时,应保持不可用状态或不提供对应 provider handler,不能将它们显示为已连接。菜单恢复本身不授权账号、不安装外部 MCP。

CSS 与最小恢复边界

固定源 CSS:主弹层 .cr-add-menu__popover width200、背景/边框/阴影清空、overflow visible;内部 .cr-menu padding8、radius16、边框0.5;row32px、font14/line22、radius8、图标16+右间距8;divider margin6px;open trigger 加背景、plus旋转45deg,transform过渡420ms cubic-bezier(.25,.8,.3,1)。SubMenu overlay 只有140ms ease-out opacity keyframe,无 scale 位移。

desktop CSS 还对 body .cr-add-menu__popover .cr-menu 和所有具体子菜单覆盖 dropdown background/border/shadow,使用 wb tokens。只复制 cr CSS 不加载该覆盖与主题变量不等于原版。实际几何/pixels还需 root 的同宽原生截图。

建议最小恢复单位为 AddMenuImpl/useAddMenuItems + Popover compound + SubMenu + Menu primitives + source CSS/主题/浮层stack,子菜单接受自身provider。子菜单必须保持两个overlay标记、portal主题和父activeSubmenuId;附件/MCP/专家/技能宿主函数在恢复UI之外接真实现有能力。不要整个执行源 bundle 初始化或以旧 WbInputAddMenu 代替新 cr 控制层。

legacy L.WbInputAddMenu 的 initializer [73372,79654) 使用独立 groups、onMouseEnter/onMouseMove、refs Map、mobile absolute/desktop fixed、多种fallback placement、relatedTarget包含关系、120ms close。它有 application 入口、新cr无该独立主入口;已选专家 click短路也属legacy。两套都无 safePolygon 文本,几何与生命周期不能互换。

验证和剩余状态

本轮仅新增此文档;未改代码/原UI。验证为模块 SHA、Babel AST函数范围、PostCSS精确选中规则+SHA和逐分支阅读。原版主菜单实际当前五项由主任务正在另采,本文不把收到的部分Evidence ID当完整已核验运行时记录。

建议 root REA 场景:单击开/关、hover相邻项、缓慢/快速跨4px缝、搜索聚焦后移开鼠标、非空Esc清搜索/空Esc回父、viewport右边/上方不足翻转、滚动child关闭、外点击及更高浮层点击、touch latch、mode留开/文件选择关全部/connector toggle留开;再比较已选cwd reference-files有结果/无结果两种。父menu及自定义row键盘是否实际聚焦、动画起止画面、每个本地picker取消返回都尚须实测。