项目版本卡按内部版本序数排序,修正初始版本被排到最后
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled

- 根因:版本投影的显示顺序走依赖视图、同层按 opaque 资源 id 做字符串排序,而 `version:initial-*` 的字符串序大于 `version:agent-*`,于是初始版本被排到最后(4 个版本读作 2/3/4/1,既与卡面「版本 N」序号冲突,也与「最新版本 = 发布标签」冲突;已落盘坐标 agent-22(0,0) agent-42(228,0) agent-46(0,168) initial-13(228,168) 为证)
- 改法:`ProjectResource` / `ResourceCanvasItem` 增加 `orderIndex`(= 内部版本序数,仅项目版本资源携带),新增 `compareResourceOrder`(先比序键、再回落 opaque id)并替换依赖视图 4 处 `compareStableIdentifier`;类型视图在 mediaType 之后插入序键比较(顺带修掉 ≥10 个版本时「版本 10」被字符串序排到「版本 2」之前的错位)
- 兼容性:不带 `orderIndex` 的其它资源排序结果与落盘坐标逐值不变
- 验证:`npx vitest run apps/ai-game-creator-shell/tests` 199 文件 / 1935 passed / 0 failed;`npm run agc:typecheck` exit 0;`npm run check:encoding` passed;`git diff --check` 干净;真机 CDP 用 Vite 现网真实模块 + 真实 manifest 复算落位:修复前 2→3→4→5→1,修复后 1→2→3→4→5
This commit is contained in:
2026-10-04 13:11:37 +08:00
parent aad8eb2aba
commit 3cca4fb99c
3 changed files with 123 additions and 4 deletions
@@ -114,6 +114,11 @@ export type ResourceCanvasItem = {
label: string;
mediaType: string;
dependencyDepth: number;
/**
* 栏目内显式次序键(可选)。同层落位先按它升序,缺省时退回 opaque `id`:
* 资源 id 只是标识,只有自带线性序的资源(如「项目版本」的 `版本 N`)才给这个键。
*/
orderIndex?: number;
cardSize?: ResourceCanvasCardSize;
};
@@ -408,6 +413,24 @@ function compareStableIdentifier(left: string, right: string) {
return left < right ? -1 : left > right ? 1 : 0;
}
/**
* 同层内的落位次序:先按资源自带的显式次序键 `orderIndex`,缺省视为同一序,再退回 opaque id。
*
* 资源 id 只是标识(`version:{versionId}` 里的 versionId 是 `initial-*` / `agent-*`),
* 拿它当次序会让「项目版本」栏目排成 `agent-*` 在前、`initial-*` 在后 —— 与卡面
* `版本 N`(内部版本序数)不一致,也看不出哪张是最新版本。只有声明了显式次序的资源
* (当前就是项目版本)受影响,其余资源一律保持原有的 id 序,落位逐值不变。
*/
function compareResourceOrder(
resourceById: ReadonlyMap<string, ResourceCanvasItem>,
leftId: string,
rightId: string,
) {
const leftOrder = resourceById.get(leftId)?.orderIndex ?? 0;
const rightOrder = resourceById.get(rightId)?.orderIndex ?? 0;
return leftOrder - rightOrder || compareStableIdentifier(leftId, rightId);
}
function compareResources(
mode: ProjectResourceCanvasLayoutMode,
left: ResourceCanvasItem,
@@ -423,6 +446,9 @@ function compareResources(
return (
compareStableIdentifier(left.subtype, right.subtype) ||
compareStableIdentifier(left.mediaType, right.mediaType) ||
// 自带显式次序的资源(项目版本)先按序数:卡面 `版本 N` 按字符串比会在 10 版之后错位
// (`版本 10` 排在 `版本 2` 前)。没有序键的资源一律退回原有的标签序。
(left.orderIndex ?? 0) - (right.orderIndex ?? 0) ||
left.label.localeCompare(right.label, 'zh-CN') ||
compareStableIdentifier(left.id, right.id)
);
@@ -817,7 +843,11 @@ function sortDependencyBucket(
}
return (
left.previousRank - right.previousRank ||
compareStableIdentifier(left.minimumResourceId, right.minimumResourceId)
compareResourceOrder(
resourceById,
left.minimumResourceId,
right.minimumResourceId,
)
);
});
resourceIds.splice(
@@ -828,7 +858,7 @@ function sortDependencyBucket(
(left, right) =>
(rankByResourceId.get(left) ?? 0) -
(rankByResourceId.get(right) ?? 0) ||
compareStableIdentifier(left, right),
compareResourceOrder(resourceById, left, right),
),
),
);
@@ -977,7 +1007,7 @@ function dependencyAutomaticPositions(
.filter((component) => !component.related)
.flatMap((component) => component.resourceIds)
.filter((resourceId) => automaticIds.has(resourceId))
.sort(compareStableIdentifier);
.sort((left, right) => compareResourceOrder(resourceById, left, right));
const clusters: DependencyCluster[] = [];
for (const component of relatedComponents) {
@@ -1058,7 +1088,9 @@ function dependencyAutomaticPositions(
idsByDepth.set(resource.dependencyDepth, ids);
}
for (const [depth, resourceIds] of idsByDepth) {
resourceIds.sort(compareStableIdentifier);
resourceIds.sort((left, right) =>
compareResourceOrder(resourceById, left, right),
);
const grid = dependencyLayerGrid(resourceIds, cardSizeByResourceId);
gridByBucket.set(resourceIds, grid);
// 层内 rank 用"网格行号",同一行的相邻卡共享纵向位置,跨层中位数对齐仍然成立。
@@ -49,6 +49,15 @@ export type ProjectResource = {
referenceResourceIds: string[];
dependencies: string[];
dependencyDepth: number;
/**
* 栏目内显式次序键:同类同层资源按它升序落位,缺省时退回 opaque 资源 id。
*
* 「项目版本」卡必须带它:卡面 `版本 N` 就是 AGC 内部版本序数(= `manifest.versions`
* 的下标 + 1,也是发布面板取的标签),而版本资源 id 是 `version:{versionId}`,
* versionId 又是 `initial-*` / `agent-*` 这类 opaque 标识。按 id 排会把初始版本
* (`initial-*` 字符串序大于 `agent-*`)排到最后,与卡面序号和「最新版本」都对不上。
*/
orderIndex?: number;
content?: string;
version?: ProjectVersionResourceSummary;
/**
@@ -530,6 +539,8 @@ export function projectResourcesFromReadModels(
referenceResourceIds: [],
dependencies: [],
dependencyDepth: 0,
// 显示次序 = `manifest.versions` 下标 = 卡面 `版本 N` 的序号 = 发布面板取的标签。
orderIndex: index,
version,
});
}