diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests/project.rs b/apps/ai-game-creator-shell/src-tauri/src/tests/project.rs
index 52ad3d47d..839132da7 100644
--- a/apps/ai-game-creator-shell/src-tauri/src/tests/project.rs
+++ b/apps/ai-game-creator-shell/src-tauri/src/tests/project.rs
@@ -2031,8 +2031,8 @@ fn register_local_asset_keeps_explicit_category_when_kind_is_unchanged() {
let registered = register_local_asset_at(
&root,
"ui/UI 设计 1.json",
- "UI",
- "application/json",
+ shared_contracts::game_creation_app::GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
+ shared_contracts::game_creation_app::GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE,
"ui-workflow",
source(),
)
@@ -2053,8 +2053,8 @@ fn register_local_asset_keeps_explicit_category_when_kind_is_unchanged() {
register_local_asset_at(
&root,
"ui/UI 设计 1.json",
- "UI",
- "application/json",
+ shared_contracts::game_creation_app::GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
+ shared_contracts::game_creation_app::GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE,
"ui-workflow",
source(),
)
@@ -2063,7 +2063,10 @@ fn register_local_asset_keeps_explicit_category_when_kind_is_unchanged() {
let manifest: Value =
serde_json::from_str(&fs::read_to_string(root.join(".agent/manifest.json")).unwrap())
.expect("manifest json");
- assert_eq!(manifest["assets"][0]["kind"], "UI");
+ assert_eq!(
+ manifest["assets"][0]["kind"],
+ shared_contracts::game_creation_app::GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND
+ );
assert_eq!(manifest["assets"][0]["category"], "audio");
assert_eq!(manifest["assets"][0]["tags"], serde_json::json!(["界面"]));
@@ -2074,7 +2077,7 @@ fn register_local_asset_keeps_explicit_category_when_kind_is_unchanged() {
///
/// 这里的字面量与写入侧逐字一致:UI 设计资产是
/// `ui_editor/resource_bridge.rs` / `workflow.rs` / `persistence.rs` 的
-/// `register_local_asset_at(root, path, "UI", "application/json", ...)`,
+/// `register_local_asset_at` 使用 UI 文档 kind/media 常量,
/// 字体是 `commands.rs` 字体上传的 `register_local_asset_entry(root, path, "font", ...)`。
/// 只要别名表漏掉它们,真机资产就会永远停在「待归类」且读时自愈也救不回来。
#[test]
@@ -2099,8 +2102,8 @@ fn register_local_asset_derives_category_from_real_write_side_kinds() {
register_local_asset_at(
&root,
"ui/UI 设计 1.json",
- "UI",
- "application/json",
+ shared_contracts::game_creation_app::GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
+ shared_contracts::game_creation_app::GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE,
"ui-workflow",
source(),
)
@@ -2133,7 +2136,11 @@ fn register_local_asset_derives_category_from_real_write_side_kinds() {
assert_eq!(
categories,
vec![
- ("UI".to_string(), "ui-interaction".to_string()),
+ (
+ shared_contracts::game_creation_app::GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND
+ .to_string(),
+ "ui-interaction".to_string(),
+ ),
("font".to_string(), "document".to_string()),
]
);
diff --git a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/persistence.rs b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/persistence.rs
index 7755cdbd3..f9f1a018a 100644
--- a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/persistence.rs
+++ b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/persistence.rs
@@ -19,8 +19,10 @@ use std::time::{SystemTime, UNIX_EPOCH};
use typed_floats::tf32::StrictlyPositiveFinite;
const UI_DESIGN_STATE_SCHEMA_VERSION: &str = "game-creator-ui-design-state.v1";
-pub(crate) const UI_DESIGN_DOC_ASSET_KIND: &str = "ui-design-doc";
-pub(crate) const UI_DESIGN_DOC_MEDIA_TYPE: &str = "application/json";
+pub(crate) use shared_contracts::game_creation_app::{
+ GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND as UI_DESIGN_DOC_ASSET_KIND,
+ GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE as UI_DESIGN_DOC_MEDIA_TYPE,
+};
const UI_DESIGN_STATE_MAX_BYTES: usize = 2 * 1024 * 1024;
const UI_DESIGN_CODE_MAX_BYTES: usize = UI_DESIGN_STATE_MAX_BYTES * 8;
const UI_DESIGN_STATE_MAX_IMAGES: usize = 4;
diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/ResourceReferenceInput.tsx b/apps/ai-game-creator-shell/src/features/project-workspace/ResourceReferenceInput.tsx
index 1971660b5..591cc3830 100644
--- a/apps/ai-game-creator-shell/src/features/project-workspace/ResourceReferenceInput.tsx
+++ b/apps/ai-game-creator-shell/src/features/project-workspace/ResourceReferenceInput.tsx
@@ -53,6 +53,7 @@ import { createPortal, flushSync } from 'react-dom';
import { PlatformResourceFilterBar } from '../../../../../packages/shared/src/components/PlatformResourceFilterBar';
import { PlatformSegmentedTabs } from '../../../../../packages/shared/src/components/PlatformSegmentedTabs';
import {
+ GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE,
type GameCreationAppAssetManifestEntry,
gameCreationAppAssetTags,
type GameIterationVersion,
@@ -1253,7 +1254,10 @@ function ResourcePickerThumbnail({
if (mediaType.startsWith('image/')) {
return ;
}
- if (kind === 'ui' || mediaType === 'application/json') {
+ if (
+ kind === 'ui' ||
+ mediaType === GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE
+ ) {
return ;
}
return ;
diff --git a/apps/ai-game-creator-shell/src/features/ui-editor/uiDesignResourceBridge.ts b/apps/ai-game-creator-shell/src/features/ui-editor/uiDesignResourceBridge.ts
index 232ca97bf..c42ca8c58 100644
--- a/apps/ai-game-creator-shell/src/features/ui-editor/uiDesignResourceBridge.ts
+++ b/apps/ai-game-creator-shell/src/features/ui-editor/uiDesignResourceBridge.ts
@@ -2,6 +2,10 @@ import type {
GameCreationAppAssetManifestEntry,
GameCreationAppManifest,
} from '../../../../../packages/shared/src/contracts/gameCreationApp';
+import {
+ GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
+ GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE,
+} from '../../../../../packages/shared/src/contracts/gameCreationApp';
export type UiDesignResourceBridgeResult = {
asset: GameCreationAppAssetManifestEntry;
@@ -48,8 +52,8 @@ export function findLinkedUiDesignResource(
manifest.assets
.filter(
(asset) =>
- asset.kind === 'ui-design-doc' &&
- asset.mediaType === 'application/json' &&
+ asset.kind === GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND &&
+ asset.mediaType === GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE &&
asset.source.referenceResourceIds?.some((reference) =>
referenceIds.has(reference),
),
diff --git a/apps/ai-game-creator-shell/src/view/project-development/index.tsx b/apps/ai-game-creator-shell/src/view/project-development/index.tsx
index 7ab94d191..7619517cd 100644
--- a/apps/ai-game-creator-shell/src/view/project-development/index.tsx
+++ b/apps/ai-game-creator-shell/src/view/project-development/index.tsx
@@ -73,6 +73,10 @@ import type {
GameIterationVersion,
ProjectResourceCanvasLayoutMode,
} from '../../../../../packages/shared/src/contracts/gameCreationApp';
+import {
+ GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
+ GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE,
+} from '../../../../../packages/shared/src/contracts/gameCreationApp';
import { ImageCanvasCharacterAnimationPanelView } from '../../../../../src/components/image-editor/ImageCanvasCharacterAnimationPanelView';
import type {
CanvasLayer,
@@ -5040,8 +5044,8 @@ export default function ProjectDevelopmentView({
if (canvasOpenEpochRef.current !== openEpoch) return;
if (
result.manifest.projectId !== manifest.projectId ||
- result.asset.kind !== 'ui-design-doc' ||
- result.asset.mediaType !== 'application/json'
+ result.asset.kind !== GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND ||
+ result.asset.mediaType !== GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE
) {
throw new Error('UI 编辑资源结果与当前项目不一致');
}
@@ -5112,8 +5116,8 @@ export default function ProjectDevelopmentView({
if (uiEditorRoute) return;
const completed = manifest.assets.find(
(asset) =>
- asset.kind === 'ui-design-doc' &&
- asset.mediaType === 'application/json' &&
+ asset.kind === GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND &&
+ asset.mediaType === GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE &&
asset.source.generationKind === 'ui-workflow.completed',
);
if (
@@ -6812,7 +6816,7 @@ export default function ProjectDevelopmentView({
[manifest, selectedResource],
);
const selectedResourceOpensUiEditor =
- selectedResource?.subtype === 'ui-design-doc' ||
+ selectedResource?.subtype === GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND ||
selectedResource?.subtype === 'ui-prototype';
const selectedToolbarStyle = selectedResourceLayer
diff --git a/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts
index 51da64e3c..8642f30d3 100644
--- a/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts
+++ b/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts
@@ -4,6 +4,10 @@ import type {
ProjectResourceCanvasLayout,
ProjectResourceCanvasPosition,
} from '../../../../packages/shared/src/contracts/gameCreationApp';
+import {
+ GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
+ GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE,
+} from '../../../../packages/shared/src/contracts/gameCreationApp';
import { RESOURCE_REFERENCE_INSERT_EVENT } from '../../src/features/project-workspace/resourceReferences';
import { RESOURCE_BOOK_OVERVIEW_STACK_LIMIT } from '../../src/view/project-development/resourceBookLayout';
import {
@@ -5908,8 +5912,8 @@ export function registerProjectWorkbenchFoundationTests() {
manifest.assets = [
{
id: 'ui-design-resource',
- kind: 'ui-design-doc',
- mediaType: 'application/json',
+ kind: GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
+ mediaType: GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE,
localPath: 'assets/ui-design.json',
source: { kind: 'generated' },
},
diff --git a/apps/ai-game-creator-shell/tests/assetKindCanonicalMapping.test.ts b/apps/ai-game-creator-shell/tests/assetKindCanonicalMapping.test.ts
index bb076aa9c..1e9dd4fff 100644
--- a/apps/ai-game-creator-shell/tests/assetKindCanonicalMapping.test.ts
+++ b/apps/ai-game-creator-shell/tests/assetKindCanonicalMapping.test.ts
@@ -10,6 +10,7 @@ import {
canonicalGameCreationAppAssetKind,
GAME_CREATION_APP_ASSET_CATEGORY_BY_KIND,
GAME_CREATION_APP_CANONICAL_ASSET_KINDS,
+ GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
type GameCreationAppAssetCategory,
gameCreationAppAssetCategory,
gameCreationAppAssetCategoryForKind,
@@ -222,8 +223,8 @@ describe('真机出现的 kind 归类口径', () => {
// UI 文档与字体 kind,两者都必须落进明确栏目。
{ kind: 'UI', canonical: 'ui-design', category: 'ui-interaction' },
{
- kind: 'ui-design-doc',
- canonical: 'ui-design-doc',
+ kind: GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
+ canonical: GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
category: 'ui-interaction',
},
{ kind: 'font', canonical: 'document', category: 'document' },
@@ -286,9 +287,11 @@ describe('写侧 kind 字面量 → 分类的端到端口径', () => {
for (const file of UI_EDITOR_WRITE_SITES) {
expect(readFileSync(file, 'utf8')).toContain('UI_DESIGN_DOC_ASSET_KIND');
}
- expect(gameCreationAppAssetCategoryForKind('ui-design-doc')).toBe(
- 'ui-interaction',
- );
+ expect(
+ gameCreationAppAssetCategoryForKind(
+ GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
+ ),
+ ).toBe('ui-interaction');
});
test('UI 编辑器写点写出的每个 kind 都落明确栏目', () => {
diff --git a/apps/ai-game-creator-shell/tests/uiDesignResourceBridge.test.ts b/apps/ai-game-creator-shell/tests/uiDesignResourceBridge.test.ts
index a7bf8eaa0..18b2a235d 100644
--- a/apps/ai-game-creator-shell/tests/uiDesignResourceBridge.test.ts
+++ b/apps/ai-game-creator-shell/tests/uiDesignResourceBridge.test.ts
@@ -4,6 +4,10 @@ import type {
GameCreationAppAssetManifestEntry,
GameCreationAppManifest,
} from '../../../packages/shared/src/contracts/gameCreationApp';
+import {
+ GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
+ GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE,
+} from '../../../packages/shared/src/contracts/gameCreationApp';
import {
ensureUiDesignResourceForPrototype,
findLinkedUiDesignResource,
@@ -37,8 +41,8 @@ describe('ui design resource bridge', () => {
const manifest = manifestWithPrototype();
manifest.assets.push({
id: 'unrelated-ui',
- kind: 'UI',
- mediaType: 'application/json',
+ kind: GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
+ mediaType: GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE,
localPath: 'ui/unrelated.json',
source: { kind: 'generated', referenceResourceIds: ['other'] },
imageSequenceFrames: null,
@@ -47,8 +51,8 @@ describe('ui design resource bridge', () => {
expect(findLinkedUiDesignResource(manifest, 'prototype-asset')).toBeNull();
manifest.assets.push({
id: 'linked-ui',
- kind: 'UI',
- mediaType: 'application/json',
+ kind: GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
+ mediaType: GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE,
localPath: 'ui/linked.json',
source: { kind: 'generated', referenceResourceIds: ['prototype-asset'] },
imageSequenceFrames: null,
@@ -64,8 +68,8 @@ describe('ui design resource bridge', () => {
manifest.assets[0]!.source.resourceId = 'canvas-resource-1';
manifest.assets.push({
id: 'workflow-ui',
- kind: 'UI',
- mediaType: 'application/json',
+ kind: GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
+ mediaType: GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE,
localPath: 'ui/workflow.json',
source: {
kind: 'generated',
@@ -85,8 +89,8 @@ describe('ui design resource bridge', () => {
manifest.assets.push(
{
id: 'bridge-ui',
- kind: 'UI',
- mediaType: 'application/json',
+ kind: GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
+ mediaType: GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE,
localPath: 'ui/UI 设计 1.json',
source: {
kind: 'generated',
@@ -97,8 +101,8 @@ describe('ui design resource bridge', () => {
},
{
id: 'completed-ui',
- kind: 'UI',
- mediaType: 'application/json',
+ kind: GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
+ mediaType: GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE,
localPath: 'ui/ui-workflow-page.json',
source: {
kind: 'generated',
@@ -118,8 +122,8 @@ describe('ui design resource bridge', () => {
const manifest = manifestWithPrototype();
const linked = {
id: 'linked-ui',
- kind: 'UI',
- mediaType: 'application/json',
+ kind: GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
+ mediaType: GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE,
localPath: 'ui/linked.json',
source: {
kind: 'generated' as const,
@@ -146,8 +150,8 @@ describe('ui design resource bridge', () => {
const result = {
asset: {
id: 'new-ui',
- kind: 'UI',
- mediaType: 'application/json',
+ kind: GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
+ mediaType: GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE,
localPath: 'ui/UI 设计 1.json',
source: {
kind: 'generated' as const,
diff --git a/packages/shared/src/contracts/gameCreationApp.ts b/packages/shared/src/contracts/gameCreationApp.ts
index 6b9a6abfb..65370e4be 100644
--- a/packages/shared/src/contracts/gameCreationApp.ts
+++ b/packages/shared/src/contracts/gameCreationApp.ts
@@ -549,7 +549,7 @@ const GAME_CREATION_APP_LEGACY_ASSET_KINDS: Record<
export function canonicalGameCreationAppAssetKind(
value: string,
): GameCreationAppCanonicalAssetKind {
- /** kind 词汇大小写不敏感;UI Editor 文档写入侧统一使用 `ui-design-doc`。 */
+ /** kind 词汇大小写不敏感;UI Editor 文档写入侧使用 UI 文档常量。 */
const normalized = value.trim().toLowerCase();
/**
* `kind` 是外部输入,可能正好是 `Object.prototype` 上的键(`constructor` / `__proto__` /
@@ -596,7 +596,7 @@ export const GAME_CREATION_APP_ASSET_CATEGORY_BY_KIND: Record<
'icon-spritesheet': 'ui-interaction',
'icon-spec': 'ui-interaction',
'ui-design': 'ui-interaction',
- 'ui-design-doc': 'ui-interaction',
+ [GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND]: 'ui-interaction',
'publication-material': 'unclassified',
spec: 'document',
video: 'unclassified',
diff --git a/server-rs/crates/shared-contracts/src/game_creation_app.rs b/server-rs/crates/shared-contracts/src/game_creation_app.rs
index 6d9fb6ddd..605f72cf6 100644
--- a/server-rs/crates/shared-contracts/src/game_creation_app.rs
+++ b/server-rs/crates/shared-contracts/src/game_creation_app.rs
@@ -5,6 +5,8 @@ pub const GAME_CREATION_APP_MANIFEST_SCHEMA_VERSION: &str = "game-creation-app.m
pub const GAME_CREATION_AGENT_RUN_SCHEMA_VERSION: &str = "game-creator-agent-run.v1";
pub const GAME_CREATION_AGENT_RUN_MAX_PASSES: u8 = 3;
pub const GAME_CREATION_AGENT_TOOL_CALL_MAX: u16 = 128;
+pub const GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND: &str = "ui-design-doc";
+pub const GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE: &str = "application/json";
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
@@ -574,7 +576,7 @@ pub const GAME_CREATION_APP_CANONICAL_ASSET_KINDS: [&str; 17] = [
"icon-spritesheet",
"icon-spec",
"ui-design",
- "ui-design-doc",
+ GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
"publication-material",
"spec",
"video",
@@ -652,7 +654,10 @@ pub const GAME_CREATION_APP_ASSET_CATEGORY_BY_KIND: [(&str, GameCreationAppAsset
),
("icon-spec", GameCreationAppAssetCategory::UiInteraction),
("ui-design", GameCreationAppAssetCategory::UiInteraction),
- ("ui-design-doc", GameCreationAppAssetCategory::UiInteraction),
+ (
+ GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
+ GameCreationAppAssetCategory::UiInteraction,
+ ),
(
"publication-material",
GameCreationAppAssetCategory::Unclassified,