重构:候选懒加载改为菜单回调,match 收成纯函数
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust smoke (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled

- ReferenceProvider 新增可选 onMenuQueryChange(query):输入区在 useEffect 里回调(菜单关闭时收到 null),作为懒加载的唯一入口
- ProviderMentionMenu 不再让 provider 在渲染期产生副作用:match 只负责过滤已就绪的数据,契约里写明它必须是纯函数
- useSkillReferenceProvider 把目录读取从 match 搬进 onMenuQueryChange,第一次敲出 $ 才读;失败仍放开重试(下一次菜单查询变化时重试)并保留 console.warn
- referenceSourceProviders 用例改为经菜单回调驱动,并显式断言 match 不触发任何 Tauri invoke、菜单关闭(null)也不触发
- 同步 ADR、决策记录与功能说明里 provider 契约的那段描述
This commit is contained in:
2026-09-22 17:24:24 +08:00
parent aa973ea34d
commit d5fb34209a
7 changed files with 52 additions and 15 deletions
@@ -3,7 +3,7 @@
## 2026-09-22 引用输入区改为宿主注入引用 provider,选择器面板与输入区分离
- 背景:`ResourceReferenceInput``apps/ai-game-creator-shell/src/features/project-workspace/ResourceReferenceInput.tsx`)同时承担「拿数据」与「编辑数据」:素材以未过滤 manifest 传入后由组件自己派生候选、显示名与「当前版本素材」scope,Skill 候选由组件自己 invoke `list_agc_skill_catalog``list_client_extensions`(只在用户敲出 `$` 时触发),素材选择面板与缩略图预览 invoke 也住在组件内部。后果是 5 个宿主(DirectProject 聊天、策划输入盒、画布生成面板、资源卡快速编辑、测试夹具)无差别获得 `$` Skill 候选,而只有 DirectProject 回合会把 `agc_skill_reference` 解析成真 SkillRust `direct_codex_user_item_to_codex_turn_input``apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/wire.rs`),其余宿主只把它退化成字面文本,形成误导入口。
- 决策(注入):输入区只接受宿主注入的 `providers: readonly ReferenceProvider[]`。每种引用一个独立工厂——`createResourceReferenceProvider({ assets })``useSkillReferenceProvider()`(Skill 候选是异步的应用级读取,所以它同时是宿主 hook:第一次 `match` 时才发起读取,结果到了宿主重渲染,读取不进输入区),附件与运行画面区域为静默 provider(无触发符、无候选);**没有总装 builder**,宿主按需选择性注入。provider 暴露 `trigger` / `match(query)` / `toReference(part)` / `refresh(reference)` / `mentionToken(part)` 与可选的 `isReady()`(数据未到齐时输入区先不把初始草稿落成文本,否则草稿里的引用会被静默丢掉),输入区只按数组顺序取第一个非空回答,不判断引用种类。
- 决策(注入):输入区只接受宿主注入的 `providers: readonly ReferenceProvider[]`。每种引用一个独立工厂——`createResourceReferenceProvider({ assets })``useSkillReferenceProvider()`(Skill 候选是异步的应用级读取,所以它同时是宿主 hook:第一次候选菜单打开时才发起读取——`match` 保持纯函数,懒加载走 `onMenuQueryChange`,由输入区在 effect 里回调;结果到了宿主重渲染,读取不进输入区),附件与运行画面区域为静默 provider(无触发符、无候选);**没有总装 builder**,宿主按需选择性注入。provider 暴露 `trigger` / `match(query)` / `toReference(part)` / `refresh(reference)` / `mentionToken(part)` 与可选的 `onMenuQueryChange(query)`(菜单懒加载的唯一入口,`match` 保持纯函数)与 `isReady()`(数据未到齐时输入区先不把初始草稿落成文本,否则草稿里的引用会被静默丢掉),输入区只按数组顺序取第一个非空回答,不判断引用种类。
- 决策(分离):素材选择面板拆成独立组件 `ResourceReferencePicker`(含一个可复用的 `@` 触发钮 `ResourceReferencePickerAction`),自己拿数据(`assets` / `versions` / `activeVersionId` / `projectPath` 与缩略图预览 invoke),由宿主渲染并把确认结果交给输入区句柄的 `insertReferences`——这是两者之间唯一的接缝。输入区删除 `versions` / `activeVersionId` / `showTriggerButton` / `openPicker` / `skills`,改为收宿主的 `inputActions`(操作排槽位,放触发钮)与 `submitSuppressed`(宿主浮层打开时 Enter 不提交,与候选菜单同一口径);`projectPath` 只保留给输入区自己的润色链路。
- 决策(附件):附件并入 `ChatReference`,编辑器收敛为单一引用节点类型,附件 chip 的 DOM 契约逐字保留;附件在**导入成功后**以芯片插入正文,`status === 'failed'` 或异常一律不插入;控制器不再持有 `attachments` 数组,`ComposerPendingAttachments` 与提交时的附件 parts 拼接一并删除,单次上限 `MAX_CHAT_COMPOSER_ATTACHMENTS = 8` 改为按草稿中的附件芯片数计算(否则上限失效),导入进行中禁止发送。
- 决策(显示口径):`directCodexContentToPromptText` 的引用 token 前后各补一个空白(相邻已是空白或相邻即另一个 token 时不重复),并加 `// TODO we will rewrite this with ref as component later.`;该函数的 `resourceId → 显示名` 反查改由调用方注入,函数自己不再读 manifest。