对账文案不再断言素材库已刷新
Project CI / Frontend tests (pull_request) Failing after 24s
Project CI / Repository checks (pull_request) Failing after 50s
Project CI / Backend tests (pull_request) Successful in 3m22s
Project CI / Native shell tests (pull_request) Failing after 7m45s

43a4e7ef7 的对账尾句写「素材库已刷新,如已生成派生图请勿重复执行」,但
refreshAssetLibrary 在 canAccessProtectedData 为 false 时直接 return,读取失败也
只在鉴权错误时弹登录框、其余一律吞掉,返回 Promise<void> 不带成败信号;它本身还是
可选 prop。三种情况下这句都是假话,而它旁边那句 Promise.resolve(...).catch() 也抓
不到任何东西——不是防御到位,是根本没有可抓的。

后果是用户读到「已刷新」却对着旧列表,判定「没有派生图,可以安全重试」,重新走回
这条修复本来要避免的重复创建,只是需要多一次失败才能到达。

改文案不改结构:刷新照旧调用(成功时用户白赚一份新列表),尾句从事实断言换成指令
「请确认素材库是否已生成派生图,再决定是否重试」,刷新失效时也成立。让
refreshAssetLibrary 返回成功标志是另一个选项,但那个 hook 多处共用,为一句文案改它
的契约不划算。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-01 13:09:27 +00:00
parent 43a4e7ef78
commit b91f57095d
3 changed files with 16 additions and 4 deletions
@@ -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<void>` 不带成败信号,且它本身是可选 prop——三种情况下「已刷新」都是假话,会让用户对着旧列表判定「没有派生图,可以重试」,重新走回这条修复要避免的重复创建。刷新照旧调用(成功时用户白赚一份新列表),但文案在刷新失效时也必须成立。
- 未覆盖:刷新页面后停在 `generating` 的占位仍无自动收口,需先给 dialog 增加「无 durable job 的 inline 链路」标记,单独立项。客户端 120 秒超时相对服务端 30 秒预算是四倍冗余,未调整。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`
@@ -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 () => {
@@ -1876,6 +1876,12 @@ export function useImageCanvasGenerationWorkflow({
if (outcomeMayBePersisted && perfectPixelDialogId) {
// 中文注释:契约要求核对「项目 / 素材」两份快照。素材库是独立记录,只 GET
// 项目却让用户去核对素材库,他看到的仍是旧列表。
//
// 但不能因为调了它就在文案里断言「素材库已刷新」:refreshAssetLibrary 在
// `canAccessProtectedData` 为 false 时直接 return,读取失败也只在鉴权错误时
// 弹登录框、其余一律吞掉,返回 `Promise<void>` 不带成败信号;它本身还是可选
// 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 &&