diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 031272abb..c5da43513 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -5980,5 +5980,6 @@ - 决策:由服务端显式告知。`AppError` 新增 `with_detail_field`,在已有 details 上补字段而不是像 `with_details` 那样整体替换,保留下游写入的 provider / message——客户端要靠 message 定位、靠新字段决策。`snap_editor_image_to_pixel_art` 在第一次 OSS PUT 之后的四条失败路径(账号素材持久化失败、项目资源缺失、账号素材缺失、画布回填失败)置 `resultPersistenceStarted: true`。客户端只对「完全无响应」和「带该标记」的失败做对账,常见的纯校验 `400`、排队 `503`、预算 `504` 既不多打读取也不附加提示。 - 配套修复:对账成功分支补上 `hasCanvasGenerationDialogById` 检查——权威快照里占位消失有两种原因,服务端消费掉或用户在请求期间删除,后者契约要求不应用完成快照、不写历史,成功路径同一处早有这道检查而对账路径漏了。对账同时刷新素材库(新增可选 `refreshAssetLibrary` 贯穿 `ImageCanvasEditorView` → surface → workflow),否则只 GET 项目却让用户核对素材库,他看到的仍是旧列表,不满足契约的「项目 / 素材快照」。占位存活分支刻意不调 `applyProjectSnapshot`:传入的是 `applyGeneratedProjectSnapshot`,其 action 默认值为 `generate-image`,不传 action 会写一条类型错误且受撤销保护的历史,而权威快照此刻与本地一致,套用只会覆盖未保存编辑。 - 验证:服务端 `assert_function_occurrence_count` 把标记钉为 4 处,并用顺序断言要求它只出现在 `persist_editor_generated_image_owned` 之后——漏标一处或误标在校验阶段都会失败。客户端新增用例覆盖「带标记的 409 触发对账并刷新素材库」「未带标记的 400 不对账、文案保持服务端原文」「用户删除占位则不应用快照不写历史」。 +- 文案边界:对账尾句只下指令、不断言素材库已刷新。`refreshAssetLibrary` 在 `canAccessProtectedData` 为 false 时直接 return,读取失败也只在鉴权错误时弹登录框、其余一律吞掉,返回 `Promise` 不带成败信号,且它本身是可选 prop——三种情况下「已刷新」都是假话,会让用户对着旧列表判定「没有派生图,可以重试」,重新走回这条修复要避免的重复创建。刷新照旧调用(成功时用户白赚一份新列表),但文案在刷新失效时也必须成立。 - 未覆盖:刷新页面后停在 `generating` 的占位仍无自动收口,需先给 dialog 增加「无 durable job 的 inline 链路」标记,单独立项。客户端 120 秒超时相对服务端 30 秒预算是四倍冗余,未调整。 - 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。 diff --git a/src/components/image-editor/useImageCanvasGenerationWorkflow.test.tsx b/src/components/image-editor/useImageCanvasGenerationWorkflow.test.tsx index 60117078b..7d1062b22 100644 --- a/src/components/image-editor/useImageCanvasGenerationWorkflow.test.tsx +++ b/src/components/image-editor/useImageCanvasGenerationWorkflow.test.tsx @@ -2376,7 +2376,8 @@ describe('useImageCanvasGenerationWorkflow', () => { const unresolvedMessage = screen.getByTestId('dialog-error').textContent ?? ''; expect(unresolvedMessage).toContain('画布未收到完美像素结果。'); - expect(unresolvedMessage).toContain('素材库已刷新'); + expect(unresolvedMessage).toContain('请确认素材库是否已生成派生图'); + expect(unresolvedMessage).not.toContain('素材库已刷新'); }); it('does not reconcile when the perfect-pixel request never left the client', async () => { @@ -2476,7 +2477,10 @@ describe('useImageCanvasGenerationWorkflow', () => { // 中文注释:服务端原文保留便于定位,对账结论与核对提示附加在后。 expect(message).toContain('画布版本冲突。'); expect(message).toContain('画布未收到完美像素结果。'); - expect(message).toContain('素材库已刷新'); + // 中文注释:只下指令、不断言素材库已刷新——refreshAssetLibrary 可能是 no-op 或 + // 静默失败,断言会让用户对着旧列表判定「没有派生图,可以重试」。 + expect(message).toContain('请确认素材库是否已生成派生图'); + expect(message).not.toContain('素材库已刷新'); }); it('skips reconciliation for a responded failure the server did not mark', async () => { diff --git a/src/components/image-editor/useImageCanvasGenerationWorkflow.ts b/src/components/image-editor/useImageCanvasGenerationWorkflow.ts index e0bf30c42..5ca2c952a 100644 --- a/src/components/image-editor/useImageCanvasGenerationWorkflow.ts +++ b/src/components/image-editor/useImageCanvasGenerationWorkflow.ts @@ -1876,6 +1876,12 @@ export function useImageCanvasGenerationWorkflow({ if (outcomeMayBePersisted && perfectPixelDialogId) { // 中文注释:契约要求核对「项目 / 素材」两份快照。素材库是独立记录,只 GET // 项目却让用户去核对素材库,他看到的仍是旧列表。 + // + // 但不能因为调了它就在文案里断言「素材库已刷新」:refreshAssetLibrary 在 + // `canAccessProtectedData` 为 false 时直接 return,读取失败也只在鉴权错误时 + // 弹登录框、其余一律吞掉,返回 `Promise` 不带成败信号;它本身还是可选 + // prop。刷新成功时用户白赚一份新列表,失败时文案必须仍然成立,所以下面只给 + // 「请确认」的指令,不给「已刷新」的事实断言。 const [reconciled] = await Promise.all([ loadEditorProject(normalizedProjectId).catch(() => null), Promise.resolve(refreshAssetLibrary?.()).catch(() => undefined), @@ -1919,9 +1925,10 @@ export function useImageCanvasGenerationWorkflow({ : undefined; // 中文注释:保留服务端原文(例如 assetKind 校验失败)便于定位,同时附上对账结论。 // 持久化非事务,对象与账号素材可能已落库而只有画布回填没完成,所以必须让用户先核对 - // 素材库再决定,而不是直接重试。 + // 素材库再决定,而不是直接重试。尾句只下指令、不断言素材库已经刷新——上面那次刷新 + // 可能是 no-op 或被静默吞掉,断言会让用户对着旧列表判定「没有派生图,可以重试」。 const errorMessage = reconciledMessage - ? `${serverMessage ? `${serverMessage} ` : ''}${reconciledMessage}素材库已刷新,如已生成派生图请勿重复执行。` + ? `${serverMessage ? `${serverMessage} ` : ''}${reconciledMessage}请确认素材库是否已生成派生图,再决定是否重试。` : (serverMessage ?? '完美像素处理失败'); if ( perfectPixelDialogId &&