From 14d16358588fc0f2ef06ecb674563ac7761fff88 Mon Sep 17 00:00:00 2001 From: Suzumiya Date: Thu, 10 Sep 2026 21:00:54 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=B8=B8=E6=88=8F=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E6=97=B6=E9=97=B4=E6=98=BE=E7=A4=BA=201970=20?= =?UTF-8?q?=E5=B9=B4=EF=BC=9AcreatedAt=20=E6=8C=89=20Unix=20=E7=A7=92?= =?UTF-8?q?=E6=8D=A2=E7=AE=97=E6=88=90=E6=AF=AB=E7=A7=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 写入侧真实单位是 Unix 秒:project/manifest.rs 的 GameIterationVersion.created_at 赋值为 unix_timestamp(),该函数返回 duration.as_secs() - formatIterationVersionLabel 原来把秒值直接交给 new Date(),被当成毫秒解释,真机因此显示 1970/1/22(1788075047 毫秒 = 1970/1/22 00:41:15,与截图逐秒吻合) - 显示侧改为 new Date(version.createdAt * 1000),与 ResourceAssetDeleteDialog 和待确认资源编辑创建时间标签的既有秒口径一致 - 给 GameIterationVersion.createdAt 补契约注释,写明单位是 Unix 秒及换算要求,避免后续再按毫秒消费 - resourceCanvasVersionBindingModel 夹具由毫秒改为真实秒值,并用真机取值 1788075047 补断言:年月日必须是 2026/8/30 而不是 1970 - resourceVersionSwitch 夹具同步改为真实秒值,避免继续沿用误导性的毫秒夹具 --- .../resourceCanvasVersionBindingModel.ts | 12 +++++- .../resourceCanvasVersionBindingModel.test.ts | 37 +++++++++++++++++-- .../tests/resourceVersionSwitch.test.tsx | 5 ++- .../shared/src/contracts/gameCreationApp.ts | 7 ++++ 4 files changed, 53 insertions(+), 8 deletions(-) diff --git a/apps/ai-game-creator-shell/src/features/resource-canvas/resourceCanvasVersionBindingModel.ts b/apps/ai-game-creator-shell/src/features/resource-canvas/resourceCanvasVersionBindingModel.ts index b65737cf1..e6dabc448 100644 --- a/apps/ai-game-creator-shell/src/features/resource-canvas/resourceCanvasVersionBindingModel.ts +++ b/apps/ai-game-creator-shell/src/features/resource-canvas/resourceCanvasVersionBindingModel.ts @@ -19,14 +19,22 @@ export const ITERATION_VERSION_REASON_LABELS: Record< 'agent-revision': '智能体修订', }; -/** 版本在 UI 里的可读标识:创建原因 + 创建时间。 */ +/** + * 版本在 UI 里的可读标识:创建原因 + 创建时间。 + * + * `GameIterationVersion.createdAt` 的单位是 **Unix 秒**(写入侧为 + * `project/manifest.rs` 的 `created_at: unix_timestamp()`,而 `unix_timestamp()` 取 + * `duration.as_secs()`)。这里必须换算成毫秒再交给 `Date`,否则秒值会被当成毫秒, + * 渲染成 1970 年(例如 1788075047 会显示成 1970/1/22)。 + * 同一约定在 `ResourceAssetDeleteDialog` 与待确认资源编辑的创建时间标签里一致使用。 + */ export function formatIterationVersionLabel( version: Pick, ) { const reason = ITERATION_VERSION_REASON_LABELS[version.createdReason] ?? version.createdReason; - const createdAt = new Date(version.createdAt); + const createdAt = new Date(version.createdAt * 1000); const time = Number.isFinite(createdAt.getTime()) ? createdAt.toLocaleString('zh-CN') : String(version.createdAt); diff --git a/apps/ai-game-creator-shell/tests/resourceCanvasVersionBindingModel.test.ts b/apps/ai-game-creator-shell/tests/resourceCanvasVersionBindingModel.test.ts index 49c606858..eff1f4445 100644 --- a/apps/ai-game-creator-shell/tests/resourceCanvasVersionBindingModel.test.ts +++ b/apps/ai-game-creator-shell/tests/resourceCanvasVersionBindingModel.test.ts @@ -6,6 +6,13 @@ import { isResourceUsedByCurrentVersion, } from '../src/features/resource-canvas/resourceCanvasVersionBindingModel'; +/** + * 夹具必须与落盘口径一致:`versions[].createdAt` 是 **Unix 秒**。 + * + * 写入侧见 `project/manifest.rs` 的 `created_at: unix_timestamp()`(`as_secs()`)。 + * `1788075047` 取自真机项目 `What do u wanna do kitten` 的 `versions[0].createdAt`, + * 它被当成毫秒渲染时正好是 `1970/1/22 00:41:15`,即真机截图里的 1970 年。 + */ const versions = [ { versionId: 'version-root', @@ -15,7 +22,7 @@ const versions = [ { slotId: 'asset:asset-player', resourceId: 'asset-player' }, ], createdReason: 'initial' as const, - createdAt: 1_760_000_000_000, + createdAt: 1_788_075_047, }, { versionId: 'version-child', @@ -27,7 +34,7 @@ const versions = [ { slotId: 'player', resourceId: 'asset-player' }, ], createdReason: 'agent-revision' as const, - createdAt: 1_760_003_600_000, + createdAt: 1_788_075_104, }, ]; @@ -80,10 +87,32 @@ describe('运行模块版本绑定判定', () => { test('labels a version with its Chinese created reason and creation time', () => { expect(formatIterationVersionLabel(versions[0])).toBe( - `初始版本 · ${new Date(1_760_000_000_000).toLocaleString('zh-CN')}`, + `初始版本 · ${new Date(1_788_075_047 * 1000).toLocaleString('zh-CN')}`, ); expect(formatIterationVersionLabel(versions[1])).toBe( - `智能体修订 · ${new Date(1_760_003_600_000).toLocaleString('zh-CN')}`, + `智能体修订 · ${new Date(1_788_075_104 * 1000).toLocaleString('zh-CN')}`, ); }); + + test('把版本 createdAt 的 Unix 秒换算成毫秒,不再渲染成 1970 年', () => { + // 期望值按同一个本地时区口径算出,用例结果不随运行机器时区漂移。 + // 1788075047 秒 = 2026-08-30T07:30:47Z。 + const createdAtLocal = new Date(1_788_075_047 * 1000).toLocaleString( + 'zh-CN', + ); + const label = formatIterationVersionLabel(versions[0]); + expect(label).toBe(`初始版本 · ${createdAtLocal}`); + + // 年 / 月 / 日必须来自真实时刻,而不是 1970。这里用显式数值选项断言, + // 不依赖 `toLocaleString` 的具体排版(不同 ICU 版本会给出不同分隔符)。 + expect( + new Intl.DateTimeFormat('zh-CN', { + year: 'numeric', + month: 'numeric', + day: 'numeric', + }).format(new Date(1_788_075_047 * 1000)), + ).toBe('2026/8/30'); + // 回归守卫:按毫秒解释同一个值会落到 1970/1/22,这正是真机截图里的错误显示。 + expect(label).not.toContain('1970'); + }); }); diff --git a/apps/ai-game-creator-shell/tests/resourceVersionSwitch.test.tsx b/apps/ai-game-creator-shell/tests/resourceVersionSwitch.test.tsx index ec60f2b9d..931034082 100644 --- a/apps/ai-game-creator-shell/tests/resourceVersionSwitch.test.tsx +++ b/apps/ai-game-creator-shell/tests/resourceVersionSwitch.test.tsx @@ -77,6 +77,7 @@ function createVersionedManifest(): GameCreationAppManifest { source: { kind: 'generated' }, }, ]; + // createdAt 是 Unix 秒(写入侧 manifest.rs 用 unix_timestamp())。 manifest.versions = [ { versionId: 'version-root', @@ -86,7 +87,7 @@ function createVersionedManifest(): GameCreationAppManifest { { slotId: 'asset:asset-player', resourceId: 'asset-player' }, ], createdReason: 'initial', - createdAt: 1_760_000_000_000, + createdAt: 1_788_075_047, }, { versionId: 'version-child', @@ -96,7 +97,7 @@ function createVersionedManifest(): GameCreationAppManifest { { slotId: 'asset:asset-town', resourceId: 'asset-town' }, ], createdReason: 'agent-revision', - createdAt: 1_760_003_600_000, + createdAt: 1_788_075_104, }, ]; return manifest; diff --git a/packages/shared/src/contracts/gameCreationApp.ts b/packages/shared/src/contracts/gameCreationApp.ts index d975d77f5..8dcc3bfdd 100644 --- a/packages/shared/src/contracts/gameCreationApp.ts +++ b/packages/shared/src/contracts/gameCreationApp.ts @@ -767,6 +767,13 @@ export interface GameIterationVersion { projectRevision: number; resourceBindings: GameIterationVersionResourceBinding[]; createdReason: GameIterationVersionCreatedReason; + /** + * 创建时间,单位是 **Unix 秒**。 + * + * 写入侧为 `project/manifest.rs` 的 `created_at: unix_timestamp()`,该函数返回 + * `duration.as_secs()`,全仓版本条目统一按秒落盘。渲染时必须换算成毫秒 + * (`new Date(createdAt * 1000)`),不要把秒值直接交给 `Date`。 + */ createdAt: number; editPrompt?: string | null; }