diff --git a/docs/project-memory/shared-memory/pitfalls.md b/docs/project-memory/shared-memory/pitfalls.md index 81ae6586f..50e67a1b8 100644 --- a/docs/project-memory/shared-memory/pitfalls.md +++ b/docs/project-memory/shared-memory/pitfalls.md @@ -653,6 +653,13 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/` - 验证:先精确运行目标用例,再以默认并行度重复运行完整 crate;失败类测试同时执行时,各实例目录只能包含自己的输入 / 输出日志,测试结束后临时目录必须清理。 - 关联:`server-rs/crates/platform-llm/src/lib.rs`、`server-rs/crates/api-server/src/creation_agent_llm_turn.rs`、`server-rs/crates/api-server/src/custom_world_foundation_draft.rs`。 +## 素材选择弹窗的打开初始化必须先于用户点击 + +- 现象:版本级资源替换用例偶发在确认后读不到替换关系;本地单独运行通过,完整前端回归或 CI 中失败。 +- 原因:弹窗打开时用普通 `useEffect` 初始化选择,用户点击素材可能先提交 `setSelection`,随后初始化 effect 又把选择清空,确认收到空数组而不会发起写入。 +- 处理:初始化查询、分类和选择改用 `useLayoutEffect`,仍只依赖 `open`,在首次绘制前完成打开态初始化,避免父级普通重渲染重置用户选择。 +- 验证:`npm test -- apps/ai-game-creator-shell/tests/resourceVersionReplacement.test.tsx --reporter=dot`;应通过该文件全部 16 条用例。 + ## 带 objectKey 的画布图片测试要等待换签后可见 - 现象:测试点击“添加素材”后,图层状态已经写入,但立即用 `getByAltText('画布图片:...')` 偶发或稳定找不到图片;前一张图可能通过,紧接着添加的第二张失败。 diff --git a/src/components/image-editor/ImageCanvasProjectAssetPickerDialog.tsx b/src/components/image-editor/ImageCanvasProjectAssetPickerDialog.tsx index fa356cd0a..e7bdca356 100644 --- a/src/components/image-editor/ImageCanvasProjectAssetPickerDialog.tsx +++ b/src/components/image-editor/ImageCanvasProjectAssetPickerDialog.tsx @@ -1,5 +1,5 @@ import { Check, ImageIcon, Music, Search, Video } from 'lucide-react'; -import { type ReactNode, useEffect, useMemo, useState } from 'react'; +import { type ReactNode, useLayoutEffect, useMemo, useState } from 'react'; import { PlatformActionButton } from '../../../packages/shared/src/components/PlatformActionButton'; import { PlatformResourceFilterBar } from '../../../packages/shared/src/components/PlatformResourceFilterBar'; @@ -133,7 +133,9 @@ export function ImageCanvasProjectAssetPickerDialog({ // `useImageCanvasGenerationSurface` 里是即时 `.filter().flatMap()`),把它放进依赖 // 会让「父级任何一次重渲染」都重新清空搜索词、分类与在选中的选择。 // `open` 翻成 true 的那一帧本身已经带着最新的初始选择,读到的就是它。 - useEffect(() => { + // 用 layout effect 在首次绘制前完成初始化,避免用户在初始化 effect 执行前点击素材, + // 随后又被初始化逻辑清空选择。 + useLayoutEffect(() => { if (!open) return; setQuery(''); setCategory('all');