From 86d55a1aba9ab7605de82616a89990f44752940e Mon Sep 17 00:00:00 2001 From: Suzumiya Date: Fri, 11 Sep 2026 20:04:33 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E8=B5=84=E6=BA=90=E7=AD=9B=E9=80=89?= =?UTF-8?q?=E6=B5=AE=E5=B1=82=E7=9A=84=20PRD=20=E6=9D=A1=E6=AC=BE=E4=B8=8E?= =?UTF-8?q?=E5=85=B1=E4=BA=AB=E5=86=B3=E7=AD=96=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - docs/prd/...PRD-2026-07-20.md:§5.2.4 新增资源筛选条款——按需弹出的独立浮层取代常驻筛选条(常驻条已移除、不回归)、固定三个字段(查找素材 / 所在区域 / 自定义标签)与各自口径、不提供「状态」字段及其理由、区域既是筛选维度也是画布当前栏目且必须是同一份状态、两种区域语义共用一份过滤实现、浮层与搜索浮层同锚点不得同时展开、关闭与高亮口径、筛选后空态要求 - docs/project-memory/shared-memory/decision-log.md:新增 2026-09-11 决策条目,按体例写背景 / 决策 / 影响范围 / 验证方式 / 关联文档 - decision-log 如实记录:已落地的部分是共享外壳、纯函数与 AGC 侧组件样式(各自独立提交),而 index.tsx 的 3 处接线因该文件被另一条线占用尚未提交,并写明接线完成后应补记——不把未落地行为写成已实现 - decision-log 记录「状态」字段不做的三条证据链(manifest 资产无 status、资源投影无 status、唯二候选任务状态与附件导入状态在投影里已被上游过滤故恒单值),以及区域用派生而非新增第二份 state、「全部区域」复用既有所有资源分组网格的取舍 - decision-log 记录 PlatformResourceFilterBar 不碰不扩的理由(横向常驻条且被主站参考图弹窗在役使用)与既有断言零放宽、三条变异验证实测结果 --- .../【AI游戏创作】项目开发工作台PRD-2026-07-20.md | 10 ++++++++++ docs/project-memory/shared-memory/decision-log.md | 13 +++++++++++++ 2 files changed, 23 insertions(+) diff --git a/docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md b/docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md index ca61aa95a..ab67f5ea7 100644 --- a/docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md +++ b/docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md @@ -324,6 +324,16 @@ type UpdateProjectResourceCanvasLayoutResult = - 任一资源出现后,普通用户资源管理固定使用 `UI 交互 -> 角色与对象 -> 场景与环境 -> 音频 -> 文档 -> 待归类 -> 项目版本` 七栏目分页画布。前六栏是 manifest 资产的功能分类 `category`(`ui-interaction / character / scene / audio / document / unclassified`),项目版本不是资源资产、6 类资产分类轴对它不适用,因此固定单独成栏;已投影资源都进入画布导航、分页、卡片、搜索与详情入口,不再有被分区轴排除的内部栏目。每个栏目按 `projectId + mode + category` 保留独立 viewport,普通 wheel 平移当前视图,`Ctrl/Cmd + wheel` 以指针位置为锚点缩放当前无限画布,空白拖动只平移当前栏目;非空状态不提供分区高度、分区内部滚动或分区内容倍率。搜索和详情开关不得重置 viewport,项目、mode 或栏目切换只恢复各自会话状态,显式复位才重新适配当前栏目内容。 - 布局 sidecar 的 `section` 允许继续出现旧四 / 五栏目取值(`code / document / version / art / audio`),读取时按资源当前分区做一次读时归并:`document / audio / version` 与新分区同名,1:1 保留;旧 `art` 是「扩展名 + mediaType」口径,按资源当前分类落入 `ui-interaction / character / scene / unclassified`;旧 `code` 落入 `unclassified`;无法精确归并时回落到 `unclassified`。归并只改写 `section`,`x`、`y` 与 `manuallyPlaced` 原样保留。读时会把 legacy 分区(`art` / `code`)坐标归并到新分区并写回一次(`revision` +1);不写迁移脚本、不重置坐标位置;但无法归并的坐标会被跳过,跳过条数连同归并条数在画布提示条上可见。 - 首次载入项目中的既有资源不显示未读标识。当前会话内,非当前栏目出现稳定 ID 的新资源时,在对应栏目名称右上角显示红点;当前栏目新增资源不显示红点,用户通过点击或程序跳转进入该栏目后立即清除。未读状态只属于当前前端会话,并按 `projectPath + projectId` 隔离,切换项目时清空,不写入 manifest、布局 sidecar 或后端。 +- 资源筛选固定为**按需弹出的独立浮层**,由右下角 Dock 上与搜索按钮同一排的筛选按钮叫出,不使用常驻筛选条(常驻条已移除,不回归)。浮层固定三个字段,竖向排布: + 1. **查找素材**:关键词,与画布搜索浮层共用同一份状态,两个入口读写同一个关键词,不得各存一份;判据沿用搜索既有的名称、路径、媒体类型、任务名四字段与大小写不敏感口径。 + 2. **所在区域**:取值为「全部区域」加上现行分区栏目(`PROJECT_RESOURCE_CANVAS_SECTIONS` 的 6 类资产分类与末尾独立的「项目版本」栏),显示名沿用 `@` 面板资源筛选那份唯一中文口径,不另建译名表。 + 3. **自定义标签**:从 manifest `assets[].tags` 派生的标签库,多选且为 AND 语义(已选标签必须全部命中,空选择不过滤),复用共享判据 `assetTagsMatchSelection`。 + 不提供「状态」字段:manifest 资产与资源投影都没有状态事实源,候选的任务状态与附件导入状态在资源投影里已被上游过滤,任何取值都恒为同一结果,因此不渲染该字段,也不保留占位下拉。 +- 「所在区域」**既是筛选维度也是画布当前栏目**,两者必须是同一份状态:区域取值由画布栏目状态派生,切换区域走与栏目切换完全相同的那一条路径,因此用户用滚轮、总览卡片或下一页切换栏目时区域显示随之变化,不需要双向同步逻辑,也不会出现两处状态互相漂移。 +- 区域取值与其它维度叠加时,筛选只作用于当前区域内的资源;「全部区域」作用于全部资源。两种语义共用同一份过滤实现,不为「只筛当前区域」另写一套裁集合逻辑。 +- 筛选浮层与画布搜索浮层使用同一个右下角锚点,两者不得同时展开:打开任一个时收起另一个。 +- 筛选浮层与搜索浮层的关闭口径一致:点击浮层外部或按 `Escape` 关闭,点触发按钮只走按钮自身的开合、不被判定为外部点击;`Escape` 在 `document` 阶段截断,画布全局 `Escape`(清选中)不随之触发;`Escape` 关闭后焦点回到触发按钮。筛选条件不随浮层收起一起消失,条件生效时 Dock 上的筛选按钮保持高亮。 +- 筛选后当前栏目内没有匹配资源时必须给出空态,不显示空白画布。 - 打开项目、切换 mode 或当前 mode 首次出现新资源时执行“读取 -> 协调 -> 必要时 CAS 写入”;dependency 模式必须先等待与当前 `projectPath + projectId + resource inputs` 匹配的 Rust 图进入 `ready` 或 `failed` 终态,等待期间不得创建 fallback、读取 sidecar、协调资源或入队保存。`failed` 只允许以空图降级初始化一次。项目或 mode 已切换后返回的旧异步结果必须丢弃。 - 同一 `projectPath + projectId + mode` 的首次读取与资源集合协调必须分开:资源集合变化不得取消已经发出的读取或保存。当前 scope 内资源自动协调写入使用单写者 FIFO,任一时刻最多一个 CAS 在途,后一笔必须使用前一笔成功返回的 revision。切换项目或 mode 后,旧 scope 的在途请求不能阻塞新 scope 队列;前端放弃旧请求槽位并丢弃其迟到响应,后端继续依靠 `expectedProjectId + expectedRevision + 系统锁` 仲裁已发出的请求。 - 自动协调 CAS 冲突时直接载入返回的最新布局;仍需协调时可以基于权威 revision 最多追加 `2` 次重试,持续跨窗口写入时不得无限自旋。当前提示只说明“布局已在其他窗口更新”,不得要求用户重新拖动。 diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 7729e5192..abc152ca3 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -15,6 +15,19 @@ - 关联文档:相关 PRD、技术文档、提交或 Issue ``` +## 2026-09-11 资源筛选改为按需弹出的独立浮层:区域与画布栏目共用一份状态,状态字段不做 + +- 背景:资源画布此前只有搜索框一条收窄路径。`bb4834520` 按用户要求移除了画布上的**常驻分类筛选条**(含画布标签 chip),随它一起摘掉的还有 `resourceCanvasCategoryFilters` / `resourceCanvasActiveTags` / `resourceTagLibrary` 三个只为常驻条存在的筛选轴,注释落在 `index.tsx` 的 `visibleResources` 上方。用户随后明确要的是**关键词 / 所在区域 / 状态 / 自定义标签**四个维度的检索,并给出原型:一个独立的「筛选」浮层、四字段竖向排布、右上角 `×` 关闭。因此本轮交付的是**按需弹出的浮层**,与已被移除的常驻条不是同一个东西;用户点名「切换区域的时候你也跟着切换区域,然后 filter 就好做了,只在当前区域 filter」。 +- 决策:**筛选固定做成独立浮层**(`PlatformFilterPanel` 只装外壳:`role="dialog"` + 标题 + 右上 `×` + 竖向字段容器,**不自带 position**,定位由宿主 `className` 注入),**不重新引入常驻筛选条**,`bb4834520` 的移除**无需撤销**。字段固定三个: + 1. **查找素材**:与画布搜索浮层**共用同一份 `searchText` 状态**,两个入口读写同一个关键词,不新建第二套搜索;判据沿用既有四字段(label / path / mediaType / taskTitle)与大小写不敏感口径。 + 2. **所在区域**:取值 = 「全部区域」+ `PROJECT_RESOURCE_CANVAS_SECTIONS` 的 6 类资产分类与末尾独立「项目版本」栏;**区域不新增第二份 state** —— 下拉 `value` 由 `resourceBookState.view === 'child' ? resourceBookState.category : 'all'` 派生,`onChange` **只调既有 `openResourceBookChild`**(与栏目切换完全同一条路径,含 viewport 拟合)。因此滚轮 / 总览卡片 / 下一页切换栏目时区域显示自动跟随,**不需要对账 effect,也不可能双写漂移**。「全部区域」复用既有 `RESOURCE_BOOK_ALL_TARGET` 的「所有资源」分组网格,8 个取值全走同一个函数、**无特例分支**;又因为 `visibleResourcesByCategory` 由筛选后的 `visibleResources` 按 `category` 派生,「区域=具体栏目时只筛该栏目」与「区域=全部时筛全量」**共用同一份过滤实现**,不为前者另写一套裁集合逻辑。 + 3. **自定义标签**:从 manifest `assets[].tags` 派生,**多选 AND**(与既有 `assetTagsMatchSelection` 一致,PRD 无条款故不偏离);标签库只统计当前区域内资源,切区域后不残留别的区域的标签。 + **不提供「状态」字段**。三条证据:① `gameCreationApp.ts` 的 `GameCreationAppAssetManifestEntry` 字段集里**没有 status**;② `resourceProjectionModel.ts` 的 `ProjectResource` 字段集里**也没有 status**;③ 唯二候选都不是资源状态且恒单值 —— `GameCreationAppTaskStatus` 是**任务**状态、而投影只让 `status === 'completed'` 的任务产出资源;`ProjectAttachmentResult.status` 是**附件导入**状态、而投影先滤掉非 `imported` 的才产出资源。硬拿它们派生,取值永远只有一个,是**假筛选维度**。按用户原话「如果没有这个东西就删掉」与仓库「四不写」,**不实现、不留占位下拉**。 +- 影响范围:`packages/shared/src/components/PlatformFilterPanel.tsx`(新增,仅表现)、`apps/ai-game-creator-shell/src/view/project-development/resourceCanvasFilterModel.ts`(新增纯函数:区域判据做恒等比较、关键词四字段、标签 AND、区域选项与显示名派生、标签库派生、筛选生效判据)、`apps/ai-game-creator-shell/src/view/project-development/ResourceFilterPanel.tsx`(新增 AGC 薄接线,领域规则留在这一层)、`apps/ai-game-creator-shell/src/styles.css`(`.game-resource-filter-panel` 右下角锚点,与搜索浮层同一 `bottom` / `z-index` 口径)。**`PlatformResourceFilterBar` 一字未改、其默认行为逐字不变**:它是**横向常驻条**且**正被主站 `src/components/image-editor/ImageCanvasProjectAssetPickerDialog.tsx` 在役使用**(参考图弹窗),把形态改成竖向会波及不在本任务范围的主站生成面,加 variant 又等于让一个组件背两种骨架,故新建外壳而非扩展它。区域显示名复用 `@` 面板那份唯一中文权威 `resourceReferenceCategoryLabel`,只为不在该 6 类表内的 `version` 单列固定显示名,不另建译名表。**跨端契约、sidecar schema、manifest 字段、后端接口均不变。** +- 未落地(截至本条记录时):`index.tsx` 的 3 处接线(①`visibleResources` 内联过滤换成调用纯函数;②新增 `resourceFilterOpen` state + Dock 筛选按钮 + 浮层挂载,并把区域接到 `openResourceBookChild`;③新增 `activeTags` state)**尚未提交**——当时该文件正被另一条线(美术画布「生成动画」接线)占用。已落地的部分(外壳、纯函数、AGC 侧组件与样式)各自独立提交,接线完成后本条应补记。 +- 验证方式:`packages/shared/src/components/PlatformFilterPanel.test.tsx` 覆盖 `dialog` 语义与关闭转发、字段逐个渲染、外壳不自带「状态」占位字段、`controlId` 字段名关联、宿主定位类注入且组件不自带 `absolute` / `fixed`;`apps/ai-game-creator-shell/tests/resourceCanvasFilterModel.test.ts` 覆盖区域选项顺序与逐个中文显示名、空筛选不重排、区域恒等比较、关键词四字段与空白归一、标签 AND 语义、无标签事实源资源的口径、三维度叠加、标签库计数与 `zh-CN` 排序、筛选生效判据;`apps/ai-game-creator-shell/tests/resourceFilterPanel.test.tsx` 覆盖三字段渲染且无「状态」占位、区域选项取值与显示名、关键词回显与回调、区域切换只回调、标签受控多选与 `aria-pressed`、空标签库隐藏该字段、关闭键 / `Escape` / 点外部 / 点触发按钮四种关闭口径、`Escape` 后焦点回触发按钮。**变异验证实测**:把区域判据改成恒 `true` → `4 failed`;去掉关键词判据 → `3 failed`;去掉标签判据 → `4 failed`;三处还原后复跑 `13 passed`。既有断言零放宽(`PlatformResourceFilterBar.test.tsx` 与主站弹窗测试一字未改,同跑 `9 passed`)。 +- 关联文档:`docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`(§5.2.4 资源筛选浮层条款)、本条上一条 2026-09-11 决策、`docs/project-memory/shared-memory/pitfalls.md`(常驻筛选条移除那条)。 + ## 2026-09-11 资源画布 sidecar 的读时归并保持写回,但不再静默,并公开丢弃口径 - 背景:2026-09-10「旧栏目坐标读时归并」那条决策在文档里写成「不写迁移脚本、不删除、不重置」,字面为真、事实上是**读时原地重写**——`useProjectResourceCanvasLayout.ts` 读盘后若 `reconcileLayout(...).changed` 为真就立刻走 `update_local_project_resource_canvas_layout` 做一次 CAS 写盘(`revision` +1)。真机量到的规模(36 份 sidecar / 300 条坐标):**112 条 legacy 分区坐标**(`art` 36×2 + `code` 20×2)会在第一次打开项目时被静默改写;另有 **82 条(27%)坐标被丢弃**,其中 **66 条**因为 `resourceId` 在 manifest 里查不到、**16 条**因为持久化分区与资源分类不匹配,丢弃点是 `resourceCanvasLayoutModel.ts` 的 `if (!normalized) return false;`,而这次丢弃同样被上面那次写回**固化**。旧文档口径与读路径真实行为冲突:不改则下一个排障的人会往「是不是有迁移脚本」的方向找。审计给出的两个候选是 A(保留重写、补可见提示 + 改文档)和 B(把丢弃/重写改成只在拖动时发生);用户拍板 **A**,因为 B 会改变布局语义、影响两次 sidecar 的既有行为。