内部版本登记后立即宣告清单失效,版本卡不再需要重开项目才出现
- 根因:版本追加只写盘、不发「清单已变」事件,而前端被当作「回合结束刷新」的那次调用实际发生在入队时(useDirectProjectChatController.ts:588-592 紧跟 startTurn 的 finally,await 的只是 enqueue_direct_codex_turn),于是 agent 追加版本后无人重读清单,新卡必须先重开项目才出现(旁证:resource-layouts/dependency.json 里 74 分钟缺该版本,直到重开项目才补齐)
- 修复:版本真正追加成功时才 emit_game_creator_manifest_invalidated(root, "direct-codex.version")(与素材工具桥同口径),订阅侧立刻重读清单
- 版本卡显示顺序:orderIndex(内部版本序数)+ compareResourceOrder,修正初始版本被排到最后(4 版读作 2/3/4/1 → 现为 1..N 单调);不带 orderIndex 的资源排序与坐标逐值不变
- 最新版本卡角标:`最新 · 发布标签 v{N}`(只给 versions 最后一条,N = versions.length = 发布面板取的标签)
- 发布面板文案:「本次发布标签 = 最新内部版本 v{N};agent 再改进一次后,下一次发布将是 v{N+1}」,并保留「平台线上号是平台侧事实、与本标签互不推导」
- 验证:vitest apps/ai-game-creator-shell/tests 1935 passed / 0 failed;cargo check(带编辑器 feature)通过;encoding / typecheck 通过;真机未验(环境重新拉起前无法核验,待补)
This commit is contained in:
@@ -4179,10 +4179,18 @@ fn sync_direct_codex_project_file_projection_at(
|
||||
return Err("Codex 返回后没有可登记的游戏文件".to_string());
|
||||
}
|
||||
update_manifest_task_status_at(root, "code-prototype", GameCreationAppTaskStatus::Completed)?;
|
||||
let mut version_registered = false;
|
||||
if outputs_changed || manifest_requires_sync {
|
||||
let _lock = acquire_project_write_lock_with_wait(root, "direct-codex.version")?;
|
||||
let version_revision = advance_agent_runtime_project_revision_locked(root)?;
|
||||
append_agent_game_iteration_version_at(root, version_revision)?;
|
||||
version_registered = append_agent_game_iteration_version_at(root, version_revision)?;
|
||||
}
|
||||
if version_registered {
|
||||
// 新增的内部版本只落在清单里:这条写入不经过任何工具桥,也就没有任何事件。
|
||||
// 必须显式宣告清单失效,否则渲染层的资源投影(「项目版本」卡、发布面板的版本标签)
|
||||
// 在本回合结束后不会重读清单 —— 版本已经在盘上,用户却看不到新的版本卡。
|
||||
// 与工具桥的素材写入同一口径(见 direct_tool_bridge.rs 的 asset-import)。
|
||||
emit_game_creator_manifest_invalidated(root, "direct-codex.version");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+17
-1
@@ -282,6 +282,22 @@ export function GameDistributionPublishPanel({
|
||||
() => resolveGameCreationAppProjectVersion(manifest),
|
||||
[manifest],
|
||||
);
|
||||
/**
|
||||
* 「项目版本」这一格的说明。标签只有一个来源(`versions[]` 条数),平台线上版本号是
|
||||
* 另一套事实:两句都要说清,且不在这里做第二份口径派生。
|
||||
*/
|
||||
const projectVersionHint = useMemo(() => {
|
||||
const internalCount = manifest.versions?.length ?? 0;
|
||||
const label =
|
||||
internalCount > 0
|
||||
? `本次发布标签 = 最新内部版本 v${projectVersion};agent 再改进一次后,下一次发布将是 v${projectVersion + 1}。`
|
||||
: `这个项目还没有内部版本,本次发布按首版 v${projectVersion}。`;
|
||||
const platform =
|
||||
latestVersionNumber > 0
|
||||
? `线上最近提交 v${latestVersionNumber} 是平台侧事实,与本标签互不推导。`
|
||||
: '第一次发布即第一版。';
|
||||
return `${label}${platform}`;
|
||||
}, [latestVersionNumber, manifest.versions, projectVersion]);
|
||||
const metadataSuggestionRequestRef = useRef(0);
|
||||
const publicationRequestRef = useRef(0);
|
||||
const publicationDraftAppliedRef = useRef(false);
|
||||
@@ -922,7 +938,7 @@ export function GameDistributionPublishPanel({
|
||||
/>
|
||||
</label>
|
||||
<span className="game-distribution-publish-panel__hint">
|
||||
{`来自 AGC 内部版本(版本 ${projectVersion}):每次智能体修订追加一个内部版本,发布标签随之推进;${latestVersionNumber > 0 ? `线上最近提交 v${latestVersionNumber} 是平台侧事实,与本标签互不推导。` : '第一次发布即第一版。'}`}
|
||||
{projectVersionHint}
|
||||
</span>
|
||||
<span
|
||||
className="game-distribution-publish-panel__metadata-hint"
|
||||
|
||||
@@ -8168,6 +8168,22 @@ iframe.preview-frame {
|
||||
-webkit-line-clamp: 3;
|
||||
}
|
||||
|
||||
/*
|
||||
* 最新内部版本卡的发布标签角标。只出现在 `versions[]` 最后一条上:它就是发布面板
|
||||
* 「项目版本」取的那一张(标签 = 内部版本序数 = 版本条数),卡面直接写出 `v{N}`。
|
||||
*/
|
||||
.game-resource-card-version-publication-tag {
|
||||
padding: 1px 7px;
|
||||
border: 1px solid rgb(201 111 68 / 38%);
|
||||
border-radius: 999px;
|
||||
background: rgb(201 111 68 / 14%);
|
||||
color: #a8492a;
|
||||
font-size: 9px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.02em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.game-resource-card-version-relations {
|
||||
color: #8e6f62;
|
||||
font-size: 9px;
|
||||
|
||||
@@ -1291,9 +1291,17 @@ const ResourceCard = memo(function ResourceCard({
|
||||
);
|
||||
}
|
||||
if (kind === 'version') {
|
||||
// 最新内部版本卡带上发布标签角标:它就是发布面板「项目版本」取的那一张,
|
||||
// 卡面直接写出 `v{N}`,用户不必再靠位置或 `版本 N` 反推。
|
||||
const publicationTag = resource.version?.publicationTag;
|
||||
return (
|
||||
<span className="game-resource-card-version-visual">
|
||||
<Gamepad2 size={34} aria-hidden="true" />
|
||||
{publicationTag ? (
|
||||
<span className="game-resource-card-version-publication-tag">
|
||||
{`最新 · 发布标签 v${publicationTag}`}
|
||||
</span>
|
||||
) : null}
|
||||
<span className="game-resource-card-version-relations">
|
||||
{resource.version?.childVersionIds.length
|
||||
? `${resource.version.childVersionIds.length} 个直接子版本`
|
||||
|
||||
@@ -32,6 +32,12 @@ export type ProjectAgentResultSummary = {
|
||||
export type ProjectVersionResourceSummary = GameIterationVersion & {
|
||||
label: string;
|
||||
childVersionIds: string[];
|
||||
/**
|
||||
* 只有**最新内部版本**(`manifest.versions` 最后一条)带它,值就是这次发布的标签
|
||||
* (= 内部版本序数 = `versions.length`,与发布面板的 `resolveGameCreationAppProjectVersion`
|
||||
* 取的是同一个数)。卡面据此渲染「最新 · 发布标签 v{N}」角标;其余版本不渲染。
|
||||
*/
|
||||
publicationTag?: number;
|
||||
};
|
||||
|
||||
export type ProjectResource = {
|
||||
@@ -506,8 +512,9 @@ export function projectResourcesFromReadModels(
|
||||
});
|
||||
}
|
||||
|
||||
const manifestVersions = manifest.versions ?? [];
|
||||
const childVersionIdsByParent = new Map<string, string[]>();
|
||||
for (const version of manifest.versions ?? []) {
|
||||
for (const version of manifestVersions) {
|
||||
if (!version.parentVersionId) {
|
||||
continue;
|
||||
}
|
||||
@@ -515,12 +522,17 @@ export function projectResourcesFromReadModels(
|
||||
children.push(version.versionId);
|
||||
childVersionIdsByParent.set(version.parentVersionId, children);
|
||||
}
|
||||
for (const [index, manifestVersion] of (manifest.versions ?? []).entries()) {
|
||||
for (const [index, manifestVersion] of manifestVersions.entries()) {
|
||||
const version: ProjectVersionResourceSummary = {
|
||||
...manifestVersion,
|
||||
label: `版本 ${index + 1}`,
|
||||
childVersionIds:
|
||||
childVersionIdsByParent.get(manifestVersion.versionId) ?? [],
|
||||
// 最新内部版本 = 数组最后一条 = 发布面板取的标签(`versions.length`)。
|
||||
// 只有它带 `publicationTag`:卡面据此渲染「最新 · 发布标签 v{N}」角标。
|
||||
...(index === manifestVersions.length - 1
|
||||
? { publicationTag: index + 1 }
|
||||
: {}),
|
||||
};
|
||||
resources.push({
|
||||
id: `version:${version.versionId}`,
|
||||
|
||||
@@ -752,7 +752,7 @@ describe('GameDistributionPublishPanel', () => {
|
||||
expect(versionInput).toHaveProperty('readOnly', true);
|
||||
expect(screen.getByText('v1')).not.toBeNull();
|
||||
expect(screen.getByText(/线上最近提交 v2 · 审核中/u)).not.toBeNull();
|
||||
expect(screen.getByText(/来自 AGC 内部版本(版本 1)/u)).not.toBeNull();
|
||||
expect(screen.getByText(/本次发布按首版 v1。/u)).not.toBeNull();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '更新游戏' }));
|
||||
await waitFor(() =>
|
||||
@@ -839,7 +839,11 @@ describe('GameDistributionPublishPanel', () => {
|
||||
expect(input).toHaveProperty('value', '4');
|
||||
expect(input).toHaveProperty('readOnly', true);
|
||||
expect(screen.getByText('v4')).not.toBeNull();
|
||||
expect(screen.getByText(/来自 AGC 内部版本(版本 4)/u)).not.toBeNull();
|
||||
expect(
|
||||
screen.getByText(
|
||||
/本次发布标签 = 最新内部版本 v4;agent 再改进一次后,下一次发布将是 v5。/u,
|
||||
),
|
||||
).not.toBeNull();
|
||||
expect(screen.getByText(/线上最近提交 v5 · 已公开/u)).not.toBeNull();
|
||||
await selectCover();
|
||||
fireEvent.click(screen.getByRole('button', { name: '更新游戏' }));
|
||||
|
||||
@@ -293,6 +293,10 @@ describe('项目资源投影', () => {
|
||||
]);
|
||||
expect(versions[0]?.version?.childVersionIds).toEqual(['version-child']);
|
||||
expect(versions[1]?.version?.parentVersionId).toBe('version-root');
|
||||
// 只有最新内部版本(数组最后一条)带发布标签:它等于 `versions.length`,
|
||||
// 也就是发布面板「项目版本」取的同一个数;角标只长在这一张卡上。
|
||||
expect(versions[0]?.version?.publicationTag).toBeUndefined();
|
||||
expect(versions[1]?.version?.publicationTag).toBe(2);
|
||||
});
|
||||
|
||||
it('按稳定优先级推导资源卡类型标识并为未知类型兜底', () => {
|
||||
|
||||
Reference in New Issue
Block a user