修复重复生成占位的对账判定
Project CI / Frontend tests (pull_request) Failing after 20s
Project CI / Repository checks (pull_request) Successful in 1m8s
Project CI / Backend tests (pull_request) Successful in 3m53s
Project CI / Native shell tests (pull_request) Successful in 10m53s

收集项目快照中全部同 ID 的生成占位原始记录。

通用队列链在任一重复占位未收口时执行第二次项目读取。

完美像素遇到重复操作占位时失败关闭为冲突。

补充两条缺陷定向测试并同步技术方案与决策记录。
This commit is contained in:
2026-08-04 05:46:51 +00:00
parent f6628ead7e
commit 06d8fbb9c2
7 changed files with 68 additions and 13 deletions
@@ -791,19 +791,20 @@ function isCanvasSettingsLayoutItem(
}
/**
* 中文注释:从快照里取出指定 id 的生成占位原始记录,取不到返回 null
* 中文注释:从快照里取出指定 id 的全部生成占位原始记录,取不到返回空数组
*
* 用原始 record 而不是 hydrate:调用方要判的是服务端写了什么,hydrate 会给缺失字段补默认值
* (例如 status 缺失时补 `idle`),把「服务端没写」和「服务端写了 idle」混成一种。
*/
export function findCanvasGenerationDialogRecord(
export function findCanvasGenerationDialogRecords(
project: EditorProjectSnapshot,
dialogId: string | null | undefined,
): Record<string, unknown> | null {
): Record<string, unknown>[] {
const normalizedDialogId = dialogId?.trim();
if (!normalizedDialogId) {
return null;
return [];
}
const matchingDialogs: Record<string, unknown>[] = [];
for (const item of project.layers) {
if (item.itemType !== 'generation-dialog') {
continue;
@@ -814,10 +815,10 @@ export function findCanvasGenerationDialogRecord(
? ((item as { dialog?: unknown }).dialog as Record<string, unknown>)
: null;
if (dialog?.id === normalizedDialogId) {
return dialog;
matchingDialogs.push(dialog);
}
}
return null;
return matchingDialogs;
}
/**
@@ -2009,7 +2009,7 @@ describe('useImageCanvasGenerationSubmissionWorkflow', () => {
);
});
it('reloads a queued project snapshot again when the completion dialog is still unresolved', async () => {
it('reloads a queued project snapshot when a later duplicate completion dialog is unresolved', async () => {
vi.useFakeTimers();
try {
const applyProjectSnapshot = vi.fn();
@@ -2020,6 +2020,16 @@ describe('useImageCanvasGenerationSubmissionWorkflow', () => {
title: '队列项目',
viewport: { x: 0, y: 0, scale: 1 },
layers: [
{
layerId: 'generation-dialog-background-removal-completed',
resourceId: 'generation-dialog-background-removal-completed',
itemType: 'generation-dialog',
dialog: {
id: 'dialog-background-removal',
status: 'idle',
generatedLayerId: 'layer-background-removal-result',
},
},
{
layerId: 'generation-dialog-background-removal',
resourceId: 'generation-dialog-background-removal',
@@ -30,7 +30,7 @@ import {
loadEditorProject,
} from '../../services/image-editor/editorProjectClient';
import {
findCanvasGenerationDialogRecord,
findCanvasGenerationDialogRecords,
isUnresolvedCanvasGenerationDialogRecord,
} from './ImageCanvasEditorModel';
import type {
@@ -456,8 +456,8 @@ function projectHasUnresolvedGenerationDialog(
project: EditorProjectSnapshot,
dialogId: string | null | undefined,
) {
return isUnresolvedCanvasGenerationDialogRecord(
findCanvasGenerationDialogRecord(project, dialogId),
return findCanvasGenerationDialogRecords(project, dialogId).some(
isUnresolvedCanvasGenerationDialogRecord,
);
}
@@ -1254,6 +1254,34 @@ describe('useImageCanvasGenerationWorkflow', () => {
).toMatchObject({ kind: 'pending', project });
});
it('rejects duplicate perfect-pixel dialog ids even when the first record appears applied', () => {
const operationId = 'perfect-pixel-duplicate-dialog';
const project = createPerfectPixelProject(operationId, 'applied');
project.layers.push({
itemType: 'generation-dialog',
layerId: `generation-dialog:${operationId}:duplicate`,
resourceId: `generation-dialog:${operationId}:duplicate`,
dialog: {
id: operationId,
mode: 'quick-edit',
prompt: '完美像素',
status: 'generating',
composerOpen: false,
},
});
expect(
inspectPerfectPixelProjectSnapshot(project, {
operationId,
taskId: `pixel-art-snap-${operationId}`,
}),
).toMatchObject({
kind: 'conflict',
project,
message: '权威项目中存在重复的完美像素占位,无法自动确认结果。',
});
});
it('requires a matching task resource before a missing dialog can prove asset-only success', () => {
const operationId = 'perfect-pixel-missing-dialog';
const matchingProject = createPerfectPixelProject(
@@ -29,7 +29,7 @@ import {
} from '../../services/image-editor/editorProjectClient';
import { resizeCropExpandFrame } from './ImageCanvasCropExpandModel';
import {
findCanvasGenerationDialogRecord,
findCanvasGenerationDialogRecords,
isUnresolvedCanvasGenerationDialogRecord,
PERFECT_PIXEL_RECONCILIATION_WINDOW_MS,
} from './ImageCanvasEditorModel';
@@ -267,7 +267,7 @@ export function inspectPerfectPixelProjectSnapshot(
const matchingResources = project.resources.filter(
(resource) => resource.taskId?.trim() === operation.taskId,
);
const dialog = findCanvasGenerationDialogRecord(
const matchingDialogs = findCanvasGenerationDialogRecords(
project,
operation.operationId,
);
@@ -279,6 +279,14 @@ export function inspectPerfectPixelProjectSnapshot(
message: '权威项目中存在重复的完美像素任务资源,无法自动确认结果。',
};
}
if (matchingDialogs.length > 1) {
return {
kind: 'conflict',
project,
message: '权威项目中存在重复的完美像素占位,无法自动确认结果。',
};
}
const dialog = matchingDialogs[0] ?? null;
const resource = matchingResources[0] ?? null;
if (!resource) {
if (!dialog || isUnresolvedCanvasGenerationDialogRecord(dialog)) {