修复 AGC 画布素材卡「引用」无消费者:新增活跃聊天输入区注册表(Issue #602) #605

Open
suzmii wants to merge 4 commits from fix/agc-canvas-reference-insert into master
Member

问题

资源画布选中已登记素材后点选中工具条的「引用」没有任何反应:聊天输入框不出现 @素材名 芯片、也没有提示。普通项目(directProjectMode)必现,立项策划链路复现不出来。

根因(Issue #602 正文):引用插入的 window 事件只有一个消费者 —— App.tsx 里的 chatComposerRef.current?.insertReferences(...),而 chatComposerRef 只赋给 PlanningChatView。2026-09-22 DirectProject 拆分引入的 directProjectMode 提前 return 让普通项目走不到那条赋值 → ref 恒为 null,可选链静默吞掉整次点击。同一批合并冲突还把 2026-09-21 的 RESOURCE_REFERENCE_INSERT_MANY_EVENT 监听整段丢了,拖拽批量引用连消费者都没有。

改动(采用 issue 推荐方案)

  1. 新增 features/project-workspace/activeChatComposer.ts:模块级只保存当前挂载的那一个输入区句柄({ insertReferences(refs), focus() });registerActiveChatComposer 返回注销函数、注销时校验身份(旧句柄不会清掉新句柄),并在检测到第二个输入区注册时留一条 dev 告警(不改运行时语义);insertChatReferences 在空批次 / 无输入区 / 句柄报「这一批没插进去」三种情况返回 false。
  2. DirectProjectComposer 用 useImperativeHandle 暴露 DirectProjectComposerHandle(按 ref 转发内部句柄,句柄本身稳定)。
  3. DirectProjectChatView 挂载期间注册按 ref 转发的句柄(注册时不读输入区是否就位,因此不依赖父子 effect 顺序)、卸载注销;PlanningChatView 同样注册(两条链路互斥渲染,同一时刻只有一个句柄)。
  4. App.tsx 收敛为一处监听:单条 + 批量两个事件都走 insertChatReferences;空批次直接返回(没有要插的东西,不报成「没有输入区」);返回 false 时 dev 下 console.warn(可选加固,已实现)。
  5. chatComposerRef 只保留给策划输入盒自己的 getDraft / clear,不再承担跨面板插入。

前后对照

  • 修复前:画布点「引用」/ 拖到对话栏 → 事件被派发 → 无人消费(可选链静默)→ 零反馈。
  • 修复后:同一个事件 → App.tsx 单处监听 → 注册表里的当前输入区 → 草稿出现 @素材名 芯片,光标落在插入之后;连点两次按顺序追加。

测试(原有用例只断言「事件被派发」,已升级为端到端)

  • tests/appSurface/project-development.suite.ts:渲染真实 DirectProject 聊天面(不再用 <div>项目总控</div> 桩),键盘 + 鼠标两条通路点「引用」后断言 form.project-chat-composer 草稿里出现 [data-resource-reference-id="scene-hero"],且第二枚接在第一枚之后。
  • tests/resourceCanvasChatReferenceDrop.test.tsx:换成真实 DirectProject 聊天面;多选拖拽断言整批一次落进草稿、顺序 = 拖动集合顺序;保留 4 条拖拽用例的「零坐标写入」断言;新增未登记素材不出「引用」按钮、拖拽落点与落点提示都给原因的用例。
  • tests/appSurface/design-agent.suite.ts:新增策划链路(PlanningChatView)引用插入不回归用例。

验证(合并 master 后实跑)

  • npx vitest run apps/ai-game-creator-shell/tests → 197 passed | 1 skipped(198 文件)、1918 passed | 17 skipped(1935 用例)
  • npx vitest run tests/activeChatComposer.test.ts tests/resourceCanvasChatReferenceDrop.test.tsx → 2 files / 12 passed
  • npx vitest run tests/appSurface.test.ts -t 引用 → 4 passed
  • npm run agc:typecheck(含 master 新增的 check:tests:types)→ exit 0
  • npm run check:encoding、git diff --check、eslint --max-warnings 0(改动文件)→ 全部通过

分支状态

  • 提交:a22e8ee44(修复)→ f4ada5061(自审加固:ref 转发注册 + 插入成功语义)→ 455b265a7(合并 master #601 的测试替身收口与 tests 类型门禁)→ 3f18e3dc7(评审三条 P2 收口)。
  • 合并 master 时三处测试文件冲突按语义两边保留:采用 master 的 installTauriRuntime / resetTauriRuntime 与类型收窄(ResourceReference 过滤),保留本分支的真实聊天面挂载与草稿断言;App 的 props 类型未导出,两处 chat 节点改用 AppComponent 别名以通过新类型门禁。
  • 新增 tests/activeChatComposer.test.ts 钉注册表合同(空批次 / 无输入区 / 句柄报落空 / 注销身份校验 / 重复注册告警 / 乱序注销)。

边界与剩余风险

  • 不改布局与样式(#600 在另一分支处理 composer 布局)。
  • 沿用现有「window 事件 + 注册表」形态;根治形态(画布与聊天的共同宿主用 context 下发 insertChatReferences)与注册表语义一致,本次不做。
  • 未跑真实客户端/CDP 冒烟(未重启用户正在跑的客户端);证伪靠 jsdom + 真实聊天面渲染。
  • 已知红(非本分支引入,也不在本分支修):master 既有共享红 —— check:nginx-spa-routes(缺 /pay、/profile/payment)、external_mcp::semantic 32 vs 30、http_tracing 偶发、AGC Rust shard 4/4 两个断言;由上游 fix/ci-master-red 分支处理,因此本分支 CI 仍会部分红,属预期。
## 问题 资源画布选中已登记素材后点选中工具条的「引用」没有任何反应:聊天输入框不出现 `@素材名` 芯片、也没有提示。普通项目(`directProjectMode`)必现,立项策划链路复现不出来。 根因(Issue #602 正文):引用插入的 window 事件只有一个消费者 —— `App.tsx` 里的 `chatComposerRef.current?.insertReferences(...)`,而 `chatComposerRef` 只赋给 `PlanningChatView`。2026-09-22 DirectProject 拆分引入的 `directProjectMode` 提前 return 让普通项目走不到那条赋值 → ref 恒为 `null`,可选链静默吞掉整次点击。同一批合并冲突还把 2026-09-21 的 `RESOURCE_REFERENCE_INSERT_MANY_EVENT` 监听整段丢了,拖拽批量引用连消费者都没有。 ## 改动(采用 issue 推荐方案) 1. 新增 `features/project-workspace/activeChatComposer.ts`:模块级只保存**当前挂载的那一个**输入区句柄(`{ insertReferences(refs), focus() }`);`registerActiveChatComposer` 返回注销函数、注销时校验身份(旧句柄不会清掉新句柄),并在检测到第二个输入区注册时留一条 dev 告警(不改运行时语义);`insertChatReferences` 在空批次 / 无输入区 / 句柄报「这一批没插进去」三种情况返回 `false`。 2. `DirectProjectComposer` 用 `useImperativeHandle` 暴露 `DirectProjectComposerHandle`(按 ref 转发内部句柄,句柄本身稳定)。 3. `DirectProjectChatView` 挂载期间注册**按 ref 转发**的句柄(注册时不读输入区是否就位,因此不依赖父子 effect 顺序)、卸载注销;`PlanningChatView` 同样注册(两条链路互斥渲染,同一时刻只有一个句柄)。 4. `App.tsx` 收敛为一处监听:**单条 + 批量两个事件都走 `insertChatReferences`**;空批次直接返回(没有要插的东西,不报成「没有输入区」);返回 `false` 时 dev 下 `console.warn`(可选加固,已实现)。 5. `chatComposerRef` 只保留给策划输入盒自己的 `getDraft` / `clear`,不再承担跨面板插入。 ## 前后对照 - 修复前:画布点「引用」/ 拖到对话栏 → 事件被派发 → 无人消费(可选链静默)→ 零反馈。 - 修复后:同一个事件 → `App.tsx` 单处监听 → 注册表里的当前输入区 → 草稿出现 `@素材名` 芯片,光标落在插入之后;连点两次按顺序追加。 ## 测试(原有用例只断言「事件被派发」,已升级为端到端) - `tests/appSurface/project-development.suite.ts`:渲染真实 DirectProject 聊天面(不再用 `<div>项目总控</div>` 桩),键盘 + 鼠标两条通路点「引用」后断言 `form.project-chat-composer` 草稿里出现 `[data-resource-reference-id="scene-hero"]`,且第二枚接在第一枚之后。 - `tests/resourceCanvasChatReferenceDrop.test.tsx`:换成真实 DirectProject 聊天面;多选拖拽断言整批一次落进草稿、顺序 = 拖动集合顺序;保留 4 条拖拽用例的「零坐标写入」断言;新增未登记素材不出「引用」按钮、拖拽落点与落点提示都给原因的用例。 - `tests/appSurface/design-agent.suite.ts`:新增策划链路(`PlanningChatView`)引用插入不回归用例。 ## 验证(合并 master 后实跑) - `npx vitest run apps/ai-game-creator-shell/tests` → 197 passed | 1 skipped(198 文件)、1918 passed | 17 skipped(1935 用例) - `npx vitest run tests/activeChatComposer.test.ts tests/resourceCanvasChatReferenceDrop.test.tsx` → 2 files / 12 passed - `npx vitest run tests/appSurface.test.ts -t 引用` → 4 passed - `npm run agc:typecheck`(含 master 新增的 `check:tests:types`)→ exit 0 - `npm run check:encoding`、`git diff --check`、eslint `--max-warnings 0`(改动文件)→ 全部通过 ## 分支状态 - 提交:`a22e8ee44`(修复)→ `f4ada5061`(自审加固:ref 转发注册 + 插入成功语义)→ `455b265a7`(合并 master #601 的测试替身收口与 tests 类型门禁)→ `3f18e3dc7`(评审三条 P2 收口)。 - 合并 master 时三处测试文件冲突按语义两边保留:采用 master 的 `installTauriRuntime` / `resetTauriRuntime` 与类型收窄(`ResourceReference` 过滤),保留本分支的真实聊天面挂载与草稿断言;`App` 的 props 类型未导出,两处 chat 节点改用 `AppComponent` 别名以通过新类型门禁。 - 新增 `tests/activeChatComposer.test.ts` 钉注册表合同(空批次 / 无输入区 / 句柄报落空 / 注销身份校验 / 重复注册告警 / 乱序注销)。 ## 边界与剩余风险 - 不改布局与样式(#600 在另一分支处理 composer 布局)。 - 沿用现有「window 事件 + 注册表」形态;根治形态(画布与聊天的共同宿主用 context 下发 `insertChatReferences`)与注册表语义一致,本次不做。 - 未跑真实客户端/CDP 冒烟(未重启用户正在跑的客户端);证伪靠 jsdom + 真实聊天面渲染。 - **已知红(非本分支引入,也不在本分支修)**:master 既有共享红 —— `check:nginx-spa-routes`(缺 `/pay`、`/profile/payment`)、`external_mcp::semantic` 32 vs 30、`http_tracing` 偶发、AGC Rust shard 4/4 两个断言;由上游 `fix/ci-master-red` 分支处理,因此本分支 CI 仍会部分红,属预期。
suzmii added 1 commit 2026-10-03 19:45:08 +08:00
修复 AGC 画布素材卡「引用」无消费者:新增活跃聊天输入区注册表
Project CI / Backend tests (pull_request) Failing after 18s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 5m44s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 3m2s
Project CI / Repository checks (pull_request) Failing after 30s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 5m10s
Project CI / Frontend tests (pull_request) Successful in 2m43s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m16s
Project CI / Native shell tests (pull_request) Successful in 5m58s
a22e8ee447
新增 features/project-workspace/activeChatComposer.ts:模块级保存当前挂载的输入区句柄,registerActiveChatComposer 注销时校验身份,insertChatReferences 空批次或无句柄返回 false
DirectProjectComposer 用 useImperativeHandle 暴露 DirectProjectComposerHandle(按 ref 转发,句柄稳定),新增可选 ref 入参
DirectProjectChatView 挂载期间注册、卸载注销,并把 composerHandleRef 传给 DirectProjectComposer
PlanningChatView 同样注册(句柄按 composerRef 转发),两条链路互斥渲染,同一时刻只有一个句柄
App.tsx 收敛为一处监听:单条「引用」与批量拖拽两个事件都走 insertChatReferences,返回 false 时 dev 下 console.warn
chatComposerRef 只保留给策划输入盒自己的 getDraft / clear,不再承担跨面板插入
project-development.suite.ts:工具条「引用」用例改为渲染真实 DirectProject 聊天面,断言草稿里出现引用芯片(键盘 + 鼠标两条通路、光标留在插入之后)
resourceCanvasChatReferenceDrop.test.tsx:换成真实 DirectProject 聊天面,批量拖拽断言整批一次落进草稿且顺序 = 拖动集合顺序;新增未登记素材不出「引用」按钮、拖拽只给原因的用例
design-agent.suite.ts:新增策划链路引用插入不回归用例
同步 docs/【功能说明】AGC聊天素材引用-2026-09-08.md、shared-memory 的 pitfalls 与 decision-log
suzmii added 1 commit 2026-10-04 00:12:43 +08:00
自审加固:注册表按 ref 转发注册、插入成功语义与失败留痕对齐
Project CI / Backend tests (pull_request) Failing after 17s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 4m50s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 5m19s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m43s
Project CI / Repository checks (pull_request) Failing after 32s
Project CI / Frontend tests (pull_request) Successful in 2m35s
Project CI / AI game creator shell web tests (pull_request) Successful in 2m23s
Project CI / Native shell tests (pull_request) Successful in 6m26s
f4ada50612
ActiveChatComposerHandle.insertReferences 改为返回 boolean:由句柄回答这一批有没有真的递到输入区,注册表不再用「有句柄」冒充「插进去了」
insertChatReferences 增加第三种 false(句柄报落空),且只有插入成功才 focus,失败不抢焦点
DirectProjectChatView 改为注册「按 ref 转发」的句柄,注册时不再读 composerHandleRef.current,去掉对父子 effect 顺序的隐式依赖(内层输入区重挂载也不会留下死句柄)
DirectProjectComposer 与 PlanningChatView 的转发句柄同步返回 boolean
App.tsx 的 dev 失败线索文案改为「没有可用的聊天输入区(未挂载或已卸载)」,覆盖句柄落空这一种
新增 tests/activeChatComposer.test.ts:钉住注册表合同(空批次 / 无输入区 / 句柄报落空不聚焦 / 注销身份校验);反向证伪:去掉身份校验后该用例变红
同步 docs/【功能说明】AGC聊天素材引用-2026-09-08.md 与 shared-memory 决策记录
suzmii added 2 commits 2026-10-04 00:38:25 +08:00
master 侧(PR #601)新增 tests/tauriRuntimeFake.ts(全仓唯一 window.__TAURI__ 安装点)与 tsconfig.tests.json,测试统一改用 installTauriRuntime / resetTauriRuntime,并新增 check:tests:types 门禁
冲突三处测试文件(resourceCanvasChatReferenceDrop.test.tsx、appSurface/project-development.suite.ts、appSurface/design-agent.suite.ts)按语义两边保留:采用 master 的替身安装点与类型收窄(ResourceReference 过滤),保留本分支的真实 DirectProject 聊天面挂载与端到端草稿断言
App 的 props 类型未导出,新增的两处 chat 节点改用 AppComponent 组件类型别名(与 design-agent.suite.ts 同一写法),通过新的 check:tests:types
docs 侧 pitfalls.md / decision-log.md 自动合并:本分支的 issue #602 条目与 master 的「根因 7/8」条目都保留
未在本分支修 master 既有红(check:nginx-spa-routes 缺 /pay 与 /profile/payment、external_mcp::semantic 32 vs 30、http_tracing 偶发、AGC Rust shard 4/4),由上游 fix/ci-master-red 处理
按独立评审的三条 P2 收口:空批次不误报、重复注册留线索、文档与实测对齐
Project CI / AI game creator shell Rust crates (pull_request) Failing after 1m35s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 4m20s
Project CI / Backend tests (pull_request) Failing after 4m39s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 5m18s
Project CI / Frontend tests (pull_request) Successful in 3m19s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m43s
Project CI / Native shell tests (pull_request) Successful in 7m28s
Project CI / Repository checks (pull_request) Failing after 4m32s
3f18e3dc77
App.tsx 的监听处对空批次直接返回:空批次没有要插的东西,不能报成「没有可用的聊天输入区」(原因指向错了方向)
activeChatComposer.registerActiveChatComposer 增加重复注册检测:dev 下 console.warn 指出后注册者顶替了前者,不改运行时语义(仍然后注册者接管、身份校验照旧兜住乱序卸载)
tests/activeChatComposer.test.ts 补两条:空批次不打扰已挂载输入区;重复注册出现告警且乱序注销清不掉更新的句柄
tests/resourceCanvasChatReferenceDrop.test.tsx 补一条:直接构造空批次引用事件时不产生「没有可用的聊天输入区」告警
decision-log 的「影响范围」补记 tests/activeChatComposer.test.ts,「验证」换成合并 master 后的实跑数字;功能说明的用例表补两行
反向证伪:去掉空批次短路、去掉重复注册告警后,上面两条新用例各红一处
Some checks are pending
Project CI / AI game creator shell Rust crates (pull_request) Failing after 1m35s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 4m20s
Project CI / Backend tests (pull_request) Failing after 4m39s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 5m18s
Project CI / Frontend tests (pull_request) Successful in 3m19s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m43s
Project CI / Native shell tests (pull_request) Successful in 7m28s
Project CI / Repository checks (pull_request) Failing after 4m32s
This pull request can be merged automatically.
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin fix/agc-canvas-reference-insert:fix/agc-canvas-reference-insert
git checkout fix/agc-canvas-reference-insert
Sign in to join this conversation.