重构/抽取带引用功能的输入框 #464

Merged
k88936 merged 13 commits from refactor/extract-dep-from-ref-inputer into master 2026-09-22 19:05:31 +08:00
Member

为了方便实现粘贴反向解析,

把待选项在使用处注入
picker 附件 作为insert行为

为了方便实现粘贴反向解析, 把待选项在使用处注入 picker 附件 作为insert行为
k88936 added 4 commits 2026-09-22 16:15:19 +08:00
- 新增 features/project-workspace/reference-source(types + 资源/Skill/附件/运行画面区域四个 provider 工厂),输入区按 providers 顺序取第一个非空回答,不再判断引用种类
- ResourceReferenceInput 删除 assets / versions / activeVersionId / skills / showTriggerButton / openPicker,改为 providers / inputActions / submitSuppressed / containerRef 四个通用接缝
- 新增 ResourceReferencePicker 与 ResourceReferencePickerAction:面板自己拿数据(素材、版本、缩略图预览 invoke)并自管筛选态,由宿主渲染,确认后只经 insertReferences 交回输入区
- 四个宿主改为按需注入 provider 并各自渲染选择器:DirectProject 注入资源+Skill+附件+运行区域,策划输入盒 / 画布生成面板 / 资源卡快速编辑只注入资源+两个静默 provider,非 DirectProject 宿主不再出现 $ Skill 候选
- 附件并入 ChatReference,编辑器收敛为单一 ResourceReferenceNode(删除 AttachmentReferenceNode),附件 chip 的 DOM 契约(data-attachment-reference / data-attachment-status / title / 移除按钮 aria-label)逐字保留
- 附件改为导入成功即以芯片进入正文、失败与异常一律不插入;控制器删除待发附件状态与 removeAttachment,ComposerPendingAttachments 与其提交时拼接一并删除,单次上限按草稿中的附件芯片数计算,导入进行中禁止发送
- directCodexContentToPromptText 的引用 token 前后各留一个空白(不重复),显示名反查改由调用方注入 resourceLabelResolver(assets),并补 TODO:后续改为按组件渲染引用
- 队列 chip 文案、快速编辑出站提示词、润色回填的引用恢复统一走 joinMentionText 的同一份 token 口径
- 新增 tests/referenceSourceProviders.test.ts 规则矩阵单测(触发符、候选过滤与截断、toReference、refresh、mentionToken、Skill 目录懒加载与失败重试),既有输入区/聊天用例迁移为注入 provider
- styles.css 删除随面板外移与待发附件列表一起失效的规则
- ProjectChatComponentProps 删除 activeVersionId:它只在策划输入盒的 @ 面板里生效,而策划输入盒的 @ 触发钮早就不渲染,参数从壳传进聊天后无人消费
- WorkspaceLauncher 不再把这个参数透传给 ProjectChat;工作台壳自己那份 activeVersionId 保留,仍供 ProjectDevelopmentView 的游戏运行版本使用
- App 的 AppProps 与解构同步删除该参数
- styles.css 里那条「ResourceReferenceInput 收到 showTriggerButton={!directCodex}」的注释改成当前口径:@ 触发钮在控制排的 .project-chat-reference-trigger,输入区不注入 ResourceReferencePickerAction
- 新增 ADR《引用候选由宿主注入》:注入式 provider、面板外移、附件并入正文的理由、备选与影响
- decision-log 增加 2026-09-22 条目:注入 / 分离 / 附件 / 显示口径四项决策、未纳入项、验证方式,并记录顺带清理 activeVersionId
- 功能说明《AGC聊天素材引用》同步:Provider 注入、ResourceReferencePicker 自持数据、$ Skill 只出现在注入该 provider 的宿主、附件进入正文与上限口径、显示名注入
- CONTEXT.md 补引用来源 / provider / 选择器 / 附件芯片等术语与关系
- docs/README.md 登记新 ADR
文档:新增引用输入区重构与宿主注入的实施计划
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m34s
Project CI / Backend tests (pull_request) Failing after 17s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m25s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 8m23s
Project CI / Repository checks (pull_request) Failing after 16s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 10m26s
Project CI / Frontend tests (pull_request) Successful in 5m37s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m6s
Project CI / Native shell tests (pull_request) Successful in 10m39s
a6a938d5cc
- 记录一句话交付、五条验收判据、六个提交切分与验证方式
- 追加执行状态:六项已落地内容、顺带清理、验证结果(全绿门禁与定向用例)与未完成项(CSS 类名重命名、真机手感、粘贴解析)
k88936 added 6 commits 2026-09-22 17:34:18 +08:00
- ResourceCanvasAssetGenerationPanelView 的 referenceProviders 改依赖 assetsSignature(referenceAssets):referenceAssets 由 .filter() 派生、每次渲染都是新数组,原来的 [referenceAssets] 依赖永远命中不了缓存
- 注释写明「provider 身份一变,输入区就重新注册 Lexical 更新监听」,避免以后又改回数组依赖
- skillReferenceProvider 的 loadSkillCatalog 失败分支补 console.warn('[skill-reference] …'),带上是哪一次重试、错在哪,排障时不再只能看到「敲 $ 没反应」
- referenceSourceProviders 用例断言这条日志(并用 vi.restoreAllMocks 收尾),把「失败可重试」与「失败留痕」两件事一起钉住
- useDirectProjectChatController 增加 attachmentsPendingCountRef:已放行但还没落成正文芯片的条数先在预算里占住名额,放行的部分在 finally 还回去
- 原来的预算只减草稿里已有的附件芯片数,两次并发导入会各自按同一份快照放行,最终插进正文的芯片可以超过 MAX_CHAT_COMPOSER_ATTACHMENTS(回归自「待发附件列表」被删掉的那次重构)
- 新增 tests/chatComposerAttachmentCap.test.tsx:两次并发各带 5 个文件,断言放行 5 + 3,并补 jsdom 缺失的 Blob.arrayBuffer
- 导出 resourceReferences 的 chatReferenceKey(只认稳定身份、不含显示名的身份键),并加注释说明它为什么不能省
- ReferenceMentionOption 由 super(chatReferenceMentionToken(...)) 改为 super(chatReferenceKey(...)):characters/hero.png 与 enemies/hero.png 都展开成 @hero,拿显示 token 当 key 会让 React 撞 key、复用错 DOM,键盘高亮与选中落到另一条候选上
- referenceSourceProviders 用例补一批同名素材:断言两条候选的显示 token 相同、身份键不同
- PlanningChatView 的 chatProjectAssets 由工作台壳每次渲染重新 .filter() 派生、DirectProjectComposer 的 assets 由 manifest 订阅每次渲染重新派生,两处的 [assets] 依赖同样从不命中缓存
- 两处改用 assetsSignature(...) 做依赖,provider 身份不再随宿主渲染抖动(流式回复时每帧都换),输入区不再每帧重注册 Lexical 更新监听
重构:候选懒加载改为菜单回调,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
d5fb34209a
- ReferenceProvider 新增可选 onMenuQueryChange(query):输入区在 useEffect 里回调(菜单关闭时收到 null),作为懒加载的唯一入口
- ProviderMentionMenu 不再让 provider 在渲染期产生副作用:match 只负责过滤已就绪的数据,契约里写明它必须是纯函数
- useSkillReferenceProvider 把目录读取从 match 搬进 onMenuQueryChange,第一次敲出 $ 才读;失败仍放开重试(下一次菜单查询变化时重试)并保留 console.warn
- referenceSourceProviders 用例改为经菜单回调驱动,并显式断言 match 不触发任何 Tauri invoke、菜单关闭(null)也不触发
- 同步 ADR、决策记录与功能说明里 provider 契约的那段描述
k88936 added 1 commit 2026-09-22 17:42:05 +08:00
Merge remote-tracking branch 'origin/master' into refactor/extract-dep-from-ref-inputer
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Failing after 21s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Failing after 20s
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
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
c42e087a30
# Conflicts:
#	docs/project-memory/shared-memory/decision-log.md
k88936 added 1 commit 2026-09-22 17:57:53 +08:00
Merge branch 'master' into refactor/extract-dep-from-ref-inputer
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m17s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m51s
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
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / Backend tests (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
1ecb92c53d
Author
Member

review.txt 处理结果(2026-09-22)

图例:- [x] = 已处理(含「核对后判定不采纳」);- [ ] = 留给你决定。

结论速览:评审 8 项已全部收口——1 / 2(日志部分)/ 3+6 / 4 / 5 已修(各一个提交),2 的冷却建议与 7 / 8 核对后判定不采纳;
另外我在核对时发现评审漏掉的同类问题一处(9),已一并修掉。
需要你点头的只剩三个可选项(见文末「留给你的」,都不是 bug)。

  • 1. ResourceCanvasAssetGenerationPanelView.tsx:269-276 —— useMemo 从不命中缓存

    • 现状:referenceAssets = resourceCanvasAssetGenerationReferenceAssets(assets ?? []).filter() 产物,
      每次渲染都是新数组;useMemo(..., [referenceAssets]) 因此每帧都重建 referenceProviders
    • 问题核实:成立。provider 身份一变,ResourceReferenceInput
      useEffect([editor, providers, refreshReferenceLabels]) 就会重跑 refreshReferenceLabels() 并重新注册一次
      Lexical update listener。画布生成面板每次按键(onChange → setContent)都会走到这里。
    • 处理:已修,提交 28bacfc12。改用 assetsSignature(referenceAssets) 做依赖,并把「为什么不能依赖数组身份」写进注释。
      安全性:签名相同 ⇒ 内容相同,provider 闭包持有的旧数组与当前清单内容一致(工厂内部本来就按签名缓存数据),不会读到过期清单。
  • 2. skillReferenceProvider.ts:78-81 —— 目录读取失败被静默吞掉

    • 现状:Promise.all([list_agc_skill_catalog, list_client_extensions]) 任一失败 → .catch
      requestedRef.current = false; setSkills([]),既不打日志也不提示用户。
    • 问题核实:日志缺失成立。「持续失败时每次按键再打两次 invoke」也成立(失败后放开重试标记,下一次菜单查询变化又会重查两份目录)。
    • 处理:日志部分已修,提交 39ac33756console.warn('[skill-reference] …', error),并补用例断言这条日志)。
      冷却/节流部分不采纳:那是「改变重试时机」的行为改动,而现有注释明确写了「一次瞬时失败不能让这个输入区此后永远拿不到技能候选」;
      在没有真实故障频次数据前加节流,等于用一个猜出来的时间窗换掉可预期的重试语义。要做的话建议给个明确窗口(例如同一轮菜单打开内只重试一次)。
  • 3. skillReferenceProvider.ts:87-88 —— match 在渲染期发起异步读取

    • 现状(修前):ProviderMentionMenuuseMemo 在渲染阶段调 provider.match(query);skill provider 的 match
      里调 ensureCatalog(),首次敲 $ 时发起 Tauri invoke,并在渲染期写 requestedRef.current
    • 问题核实:成立(StrictMode 双调渲染下第一次会真的打两次目录;被丢弃的那次渲染也可能触发 invoke)。
    • 处理:已按 A 方案修,提交 d5fb34209
      • ReferenceProvider 新增可选 onMenuQueryChange?(query)(菜单关闭时收到 null),由 ProviderMentionMenu
        useEffect 里回调;契约里写明 match 必须是纯函数
      • skill provider 的目录读取从 match 搬进 onMenuQueryChangequery !== null 时才 ensureCatalog()),
        「第一次敲出 $ 才读、挂载即查询不产生后端访问」的边界保留;
      • 用例改成经菜单回调驱动,并显式断言 match 不触发任何 Tauri invoke、菜单关闭(null)也不触发;
      • 同步 ADR / 决策记录 / 功能说明里 provider 契约的那段描述。
  • 4. useDirectProjectChatController.ts:270 —— 并发导入可以突破 8 个附件上限

    • 现状:uploadFiles(files, draftAttachmentCount) 用宿主在导入开始时读到的快照算预算,控制器自己不记「在飞的条数」;
      ComposerAttachmentMenu 在导入中不禁用,宿主对返回结果无条件 insertReferences
    • 问题核实:成立,且是本轮重构引入的回归。重构前是
      setAttachments(current => [...current, ...next].slice(0, MAX)),硬上限成立;改成「导入成功即入正文」后
      再没有这一道收口,两次并发各带 5 个文件就能插进去 10 个芯片。
    • 处理:已修,提交 3fdde6f51。控制器加 attachmentsPendingCountRef:已放行、还没落成芯片的条数先在预算里占名额,
      放行的部分在 finally 还回去。语义取舍:预算不够时拒绝这次(沿用既有「最多同时携带 8 个附件,请先移除已有附件」文案),
      而不是「先收下再在插入时砍掉」——后者会让「已上传 N 个文件」的提示与实际芯片数对不上。
      同时补了 tests/chatComposerAttachmentCap.test.tsx(两次并发各 5 个 → 放行 5 + 3),并确认这条用例在没修之前是红的。
  • 5. ResourceReferenceInput.tsx:123-128 —— 候选菜单的 key 用了显示 token

    • 现状:ReferenceMentionOptionsuper(chatReferenceMentionToken(reference)),即 @显示名 / $名称 当 key。
    • 问题核实:成立,也是本轮重构引入的回归。我按 git show HEAD~4 查了重构前的实现:
      资源是 super(reference.resourceId)、Skill 是 super(skill.name),都是唯一身份。
      现在 characters/hero.pngenemies/hero.png 都展开成 @hero,同一候选列表里会出现重复 key
      (Lexical 的 MenuOption 契约本身写着 key 必须唯一),React 复用 DOM 会让键盘高亮/选中落到另一条候选上。
    • 处理:已修,提交 fbfc7e5e6。导出 resourceReferences.chatReferenceKey(只认稳定身份、不含显示名)复用它做 key,
      显示 token 只留给 labelreferenceSourceProviders 用例补一批同名素材,断言「显示 token 相同、身份键不同」。
      说明:DOM 级守护(重复 key 告警)没做成用例——jsdom 里候选浮层需要 ResizeObserverRange.getBoundingClientRect
      两个垫片才能渲染,目前仓里也没有任何用例渲染过带候选的 @ 浮层。要的话我补垫片再加一条。
  • 6. ResourceReferenceInput.tsx:796-801 —— 与第 3 项同源,已随 A 方案一起修

    • 说明:渲染期调 match 的问题已由 onMenuQueryChange 收口;match 现在是纯函数,
      ProviderMentionMenu 只剩「把 query 变化回调出去」与「用已就绪的数据生成候选」两件事。提交同 d5fb34209
  • 7. ResourceReferenceInput.tsx:339-342 —— 渲染期写 providersRef / submitSuppressedRef

    • 现状:providersRef.current = providers; submitSuppressedRef.current = submitSuppressed; 写在渲染体里。
    • 判定:不采纳(评审这条偏教条、且换法更差)。这两个 ref 的目的是「命令回调/宿主命令式调用读到最新 props」,
      写入的是 props 派生值、幂等;改成 useEffect 同步会在「commit 完但 effect 还没跑」的窗口里留下过期值,
      而读它的既有 Lexical 命令回调与 useImperativeHandle 句柄都不是 effect。React 19.2.4 确实有 useEffectEvent
      但它的语义是「只能在 effect 里调用」,用在这里不成立。
  • 8. ResourceReferencePicker.tsx:159-162 —— 渲染期写 onOpenChangeRef / onInsertRef

    • 现状与判定:同第 7 项(都是「最新回调 ref」),不采纳。补充一点:面板的 open() 由宿主经命令式句柄调用,
      回调必须是调用那一刻的最新值,用 effect 同步只会引入过期窗口。
  • 9. 【评审没提,我核对第 1 项时发现的同类问题】PlanningChatView 与 DirectProjectComposer 的 provider memo 也从不命中

    • 现状:PlanningChatViewchatProjectAssets 由工作台壳(App.tsxmanifest.assets.filter(...))每次渲染重新派生;
      DirectProjectComposerassetsuseDirectProjectManifest 每次渲染重新 .filter() 派生。两处 useMemo 依赖同样是数组身份。
    • 问题:与评审第 1 项同源,而且这两处更频繁——宿主每帧渲染(流式回复逐 token 更新)都会让输入区重注册 Lexical 监听。
    • 处理:已修,提交 aa973ea34。两处都改成按 assetsSignature(...) 记忆。
  • 第 2 项的冷却/节流:要不要给 Skill 目录失败加重试节流窗口(会改重试时机,见第 2 项)。

  • 第 5 项的可选补强:jsdom 垫片(ResizeObserver + Range.getBoundingClientRect)之后补一条「候选浮层无重复 key 告警」的用例。

  • 文档可选微调:功能说明里「单次上限按草稿里已有的附件芯片数计算」可以补一句「并发导入时已放行的条数也先占名额」,
    我这次没动文档(避免为一句实现细节再开提交),要的话我补。

# review.txt 处理结果(2026-09-22) 图例:`- [x]` = 已处理(含「核对后判定不采纳」);`- [ ]` = 留给你决定。 结论速览:评审 8 项已全部收口——1 / 2(日志部分)/ 3+6 / 4 / 5 已修(各一个提交),2 的冷却建议与 7 / 8 核对后判定不采纳; 另外我在核对时发现评审漏掉的同类问题一处(9),已一并修掉。 需要你点头的只剩三个可选项(见文末「留给你的」,都不是 bug)。 - [x] 1. ResourceCanvasAssetGenerationPanelView.tsx:269-276 —— `useMemo` 从不命中缓存 - 现状:`referenceAssets = resourceCanvasAssetGenerationReferenceAssets(assets ?? [])` 是 `.filter()` 产物, 每次渲染都是新数组;`useMemo(..., [referenceAssets])` 因此每帧都重建 `referenceProviders`。 - 问题核实:成立。provider 身份一变,`ResourceReferenceInput` 里 `useEffect([editor, providers, refreshReferenceLabels])` 就会重跑 `refreshReferenceLabels()` 并重新注册一次 Lexical update listener。画布生成面板每次按键(`onChange → setContent`)都会走到这里。 - 处理:已修,提交 `28bacfc12`。改用 `assetsSignature(referenceAssets)` 做依赖,并把「为什么不能依赖数组身份」写进注释。 安全性:签名相同 ⇒ 内容相同,provider 闭包持有的旧数组与当前清单内容一致(工厂内部本来就按签名缓存数据),不会读到过期清单。 - [x] 2. skillReferenceProvider.ts:78-81 —— 目录读取失败被静默吞掉 - 现状:`Promise.all([list_agc_skill_catalog, list_client_extensions])` 任一失败 → `.catch` 里 `requestedRef.current = false; setSkills([])`,既不打日志也不提示用户。 - 问题核实:日志缺失成立。「持续失败时每次按键再打两次 invoke」也成立(失败后放开重试标记,下一次菜单查询变化又会重查两份目录)。 - 处理:日志部分已修,提交 `39ac33756`(`console.warn('[skill-reference] …', error)`,并补用例断言这条日志)。 **冷却/节流部分不采纳**:那是「改变重试时机」的行为改动,而现有注释明确写了「一次瞬时失败不能让这个输入区此后永远拿不到技能候选」; 在没有真实故障频次数据前加节流,等于用一个猜出来的时间窗换掉可预期的重试语义。要做的话建议给个明确窗口(例如同一轮菜单打开内只重试一次)。 - [x] 3. skillReferenceProvider.ts:87-88 —— `match` 在渲染期发起异步读取 - 现状(修前):`ProviderMentionMenu` 用 `useMemo` 在渲染阶段调 `provider.match(query)`;skill provider 的 `match` 里调 `ensureCatalog()`,首次敲 `$` 时发起 Tauri invoke,并在渲染期写 `requestedRef.current`。 - 问题核实:成立(StrictMode 双调渲染下第一次会真的打两次目录;被丢弃的那次渲染也可能触发 invoke)。 - 处理:已按 **A 方案**修,提交 `d5fb34209`: - `ReferenceProvider` 新增可选 `onMenuQueryChange?(query)`(菜单关闭时收到 `null`),由 `ProviderMentionMenu` 在 `useEffect` 里回调;契约里写明 `match` **必须是纯函数**; - skill provider 的目录读取从 `match` 搬进 `onMenuQueryChange`(`query !== null` 时才 `ensureCatalog()`), 「第一次敲出 `$` 才读、挂载即查询不产生后端访问」的边界保留; - 用例改成经菜单回调驱动,并显式断言 `match` 不触发任何 Tauri invoke、菜单关闭(`null`)也不触发; - 同步 ADR / 决策记录 / 功能说明里 provider 契约的那段描述。 - [x] 4. useDirectProjectChatController.ts:270 —— 并发导入可以突破 8 个附件上限 - 现状:`uploadFiles(files, draftAttachmentCount)` 用宿主在导入开始时读到的快照算预算,控制器自己不记「在飞的条数」; `ComposerAttachmentMenu` 在导入中不禁用,宿主对返回结果无条件 `insertReferences`。 - 问题核实:成立,且是本轮重构引入的回归。重构前是 `setAttachments(current => [...current, ...next].slice(0, MAX))`,硬上限成立;改成「导入成功即入正文」后 再没有这一道收口,两次并发各带 5 个文件就能插进去 10 个芯片。 - 处理:已修,提交 `3fdde6f51`。控制器加 `attachmentsPendingCountRef`:已放行、还没落成芯片的条数先在预算里占名额, 放行的部分在 `finally` 还回去。语义取舍:预算不够时**拒绝**这次(沿用既有「最多同时携带 8 个附件,请先移除已有附件」文案), 而不是「先收下再在插入时砍掉」——后者会让「已上传 N 个文件」的提示与实际芯片数对不上。 同时补了 `tests/chatComposerAttachmentCap.test.tsx`(两次并发各 5 个 → 放行 5 + 3),并确认这条用例在没修之前是红的。 - [x] 5. ResourceReferenceInput.tsx:123-128 —— 候选菜单的 key 用了显示 token - 现状:`ReferenceMentionOption` 用 `super(chatReferenceMentionToken(reference))`,即 `@显示名` / `$名称` 当 key。 - 问题核实:成立,也是本轮重构引入的回归。我按 `git show HEAD~4` 查了重构前的实现: 资源是 `super(reference.resourceId)`、Skill 是 `super(skill.name)`,都是唯一身份。 现在 `characters/hero.png` 与 `enemies/hero.png` 都展开成 `@hero`,同一候选列表里会出现重复 key (Lexical 的 `MenuOption` 契约本身写着 key 必须唯一),React 复用 DOM 会让键盘高亮/选中落到另一条候选上。 - 处理:已修,提交 `fbfc7e5e6`。导出 `resourceReferences.chatReferenceKey`(只认稳定身份、不含显示名)复用它做 key, 显示 token 只留给 `label`;`referenceSourceProviders` 用例补一批同名素材,断言「显示 token 相同、身份键不同」。 说明:DOM 级守护(重复 key 告警)没做成用例——jsdom 里候选浮层需要 `ResizeObserver` 与 `Range.getBoundingClientRect` 两个垫片才能渲染,目前仓里也没有任何用例渲染过带候选的 `@` 浮层。要的话我补垫片再加一条。 - [x] 6. ResourceReferenceInput.tsx:796-801 —— 与第 3 项同源,已随 A 方案一起修 - 说明:渲染期调 `match` 的问题已由 `onMenuQueryChange` 收口;`match` 现在是纯函数, `ProviderMentionMenu` 只剩「把 query 变化回调出去」与「用已就绪的数据生成候选」两件事。提交同 `d5fb34209`。 - [x] 7. ResourceReferenceInput.tsx:339-342 —— 渲染期写 `providersRef` / `submitSuppressedRef` - 现状:`providersRef.current = providers; submitSuppressedRef.current = submitSuppressed;` 写在渲染体里。 - 判定:**不采纳**(评审这条偏教条、且换法更差)。这两个 ref 的目的是「命令回调/宿主命令式调用读到最新 props」, 写入的是 props 派生值、幂等;改成 `useEffect` 同步会在「commit 完但 effect 还没跑」的窗口里留下过期值, 而读它的既有 Lexical 命令回调与 `useImperativeHandle` 句柄都不是 effect。React 19.2.4 确实有 `useEffectEvent`, 但它的语义是「只能在 effect 里调用」,用在这里不成立。 - [x] 8. ResourceReferencePicker.tsx:159-162 —— 渲染期写 `onOpenChangeRef` / `onInsertRef` - 现状与判定:同第 7 项(都是「最新回调 ref」),**不采纳**。补充一点:面板的 `open()` 由宿主经命令式句柄调用, 回调必须是调用那一刻的最新值,用 effect 同步只会引入过期窗口。 - [x] 9. 【评审没提,我核对第 1 项时发现的同类问题】PlanningChatView 与 DirectProjectComposer 的 provider memo 也从不命中 - 现状:`PlanningChatView` 的 `chatProjectAssets` 由工作台壳(`App.tsx` 的 `manifest.assets.filter(...)`)每次渲染重新派生; `DirectProjectComposer` 的 `assets` 由 `useDirectProjectManifest` 每次渲染重新 `.filter()` 派生。两处 `useMemo` 依赖同样是数组身份。 - 问题:与评审第 1 项同源,而且这两处更频繁——宿主每帧渲染(流式回复逐 token 更新)都会让输入区重注册 Lexical 监听。 - 处理:已修,提交 `aa973ea34`。两处都改成按 `assetsSignature(...)` 记忆。 - 第 2 项的冷却/节流:要不要给 Skill 目录失败加重试节流窗口(会改重试时机,见第 2 项)。 - 第 5 项的可选补强:jsdom 垫片(`ResizeObserver` + `Range.getBoundingClientRect`)之后补一条「候选浮层无重复 key 告警」的用例。 - 文档可选微调:功能说明里「单次上限按草稿里已有的附件芯片数计算」可以补一句「并发导入时已放行的条数也先占名额」, 我这次没动文档(避免为一句实现细节再开提交),要的话我补。
k88936 marked the pull request as ready for review 2026-09-22 18:00:14 +08:00
k88936 added 1 commit 2026-09-22 18:51:55 +08:00
Merge remote-tracking branch 'refs/remotes/origin/master' into refactor/extract-dep-from-ref-inputer
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m25s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m56s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 6m51s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 8m30s
Project CI / Backend tests (pull_request) Successful in 7m17s
Project CI / Native shell tests (pull_request) Successful in 7m34s
Project CI / Frontend tests (pull_request) Successful in 4m4s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m9s
Project CI / Repository checks (pull_request) Successful in 3m36s
9ae9ed362a
# Conflicts:
#	docs/project-memory/shared-memory/decision-log.md
k88936 merged commit da0df57ebc into master 2026-09-22 19:05:31 +08:00
k88936 deleted branch refactor/extract-dep-from-ref-inputer 2026-09-22 19:05:31 +08:00
Sign in to join this conversation.