diff --git a/apps/ai-game-creator-shell/scripts/build-release.mjs b/apps/ai-game-creator-shell/scripts/build-release.mjs index 85d4b63a5..38d6d1c5c 100644 --- a/apps/ai-game-creator-shell/scripts/build-release.mjs +++ b/apps/ai-game-creator-shell/scripts/build-release.mjs @@ -386,6 +386,9 @@ export function createChannelConfig( return { productName, identifier, + app: { + windows: [{ title: productName }], + }, plugins: { updater: { endpoints: [updateManifestUrl(channel, target)], @@ -452,6 +455,8 @@ export function runTauriBuild( // Vite embeds the platform API origin in the packaged renderer. The // release channel and updater channel therefore cannot drift apart. VITE_AGC_PLATFORM_CHANNEL: channel, + VITE_AGC_PRODUCT_NAME: + resolveChannelInstallIdentity(channel).productName, }, }, ); diff --git a/apps/ai-game-creator-shell/scripts/build-release.test.mjs b/apps/ai-game-creator-shell/scripts/build-release.test.mjs index fefd89e67..4c62cd8b4 100644 --- a/apps/ai-game-creator-shell/scripts/build-release.test.mjs +++ b/apps/ai-game-creator-shell/scripts/build-release.test.mjs @@ -177,8 +177,11 @@ test('channel manifest URL and build-time endpoint follow the channel', () => { 'https://agc-dev.oss-rg-china-mainland.aliyuncs.com/agc/dev-win/latest.json', ); assert.deepEqual(createChannelConfig('dev', 'aarch64-apple-darwin'), { - productName: AGC_PRODUCT_NAME, + productName: `${AGC_PRODUCT_NAME}开发版`, identifier: AGC_APP_IDENTIFIER, + app: { + windows: [{ title: `${AGC_PRODUCT_NAME}开发版` }], + }, plugins: { updater: { endpoints: [ @@ -202,7 +205,7 @@ test('channel manifest URL and build-time endpoint follow the channel', () => { test('channel install identity isolates co-installed builds and keeps the default channel stable', () => { // 默认渠道必须保持已发布客户端身份:改身份等于换一个 App,升级链会断。 assert.deepEqual(resolveChannelInstallIdentity('dev'), { - productName: AGC_PRODUCT_NAME, + productName: `${AGC_PRODUCT_NAME}开发版`, identifier: AGC_APP_IDENTIFIER, }); assert.deepEqual(resolveChannelInstallIdentity('release'), { @@ -277,6 +280,10 @@ test('packaged renderer receives the same channel as the updater manifest', () = }, }); assert.equal(spawnOptions?.env?.VITE_AGC_PLATFORM_CHANNEL, 'release'); + assert.equal( + spawnOptions?.env?.VITE_AGC_PRODUCT_NAME, + `${AGC_PRODUCT_NAME} Release`, + ); }); test('macOS manifests advertise exactly the architectures actually built', () => { diff --git a/apps/ai-game-creator-shell/scripts/channel-identity.mjs b/apps/ai-game-creator-shell/scripts/channel-identity.mjs index 5de68db22..db90f9c37 100644 --- a/apps/ai-game-creator-shell/scripts/channel-identity.mjs +++ b/apps/ai-game-creator-shell/scripts/channel-identity.mjs @@ -10,7 +10,7 @@ * 因此不同渠道的包体在同一台设备上并存时互不顶掉,也不会共享登录态、 * 本地项目与运行锁。 * - * 默认渠道 `dev` 保持已发布客户端身份不变:升级链路与既有安装不能断。 + * 默认渠道 `dev` 保持已发布客户端标识不变:升级链路与既有安装不能断;展示名显式标记为开发版。 */ export const AGC_DEFAULT_CHANNEL = 'dev'; @@ -46,8 +46,9 @@ export function resolveReleaseChannel(env = process.env) { return validateReleaseChannel(env.AGC_UPDATE_CHANNEL?.trim() ?? 'dev'); } -/** 安装身份里的展示后缀:`release` → `Release`,`beta-2` → `Beta-2`。 */ +/** 安装身份里的展示后缀:`dev` → `开发版`,`release` → `Release`,`beta-2` → `Beta-2`。 */ export function channelDisplaySuffix(channel) { + if (channel === AGC_DEFAULT_CHANNEL) return '开发版'; return validateReleaseChannel(channel) .split('-') .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)) @@ -55,19 +56,19 @@ export function channelDisplaySuffix(channel) { } /** - * 渠道对应的安装身份。默认渠道返回基线身份,其它渠道派生渠道后缀, - * 保证同一台设备上不同渠道互不覆盖。 + * 渠道对应的安装身份。默认渠道保持既有 identifier 以兼容已安装客户端, + * 但展示名明确标记为开发版;其它渠道派生独立 identifier,保证同一台设备上并存。 */ export function resolveChannelInstallIdentity(channel = AGC_DEFAULT_CHANNEL) { validateReleaseChannel(channel); - if (channel === AGC_DEFAULT_CHANNEL) { - return Object.freeze({ - productName: AGC_PRODUCT_NAME, - identifier: AGC_APP_IDENTIFIER, - }); - } return Object.freeze({ - productName: `${AGC_PRODUCT_NAME} ${channelDisplaySuffix(channel)}`, - identifier: `${AGC_APP_IDENTIFIER}.${channel}`, + productName: + channel === AGC_DEFAULT_CHANNEL + ? `${AGC_PRODUCT_NAME}开发版` + : `${AGC_PRODUCT_NAME} ${channelDisplaySuffix(channel)}`, + identifier: + channel === AGC_DEFAULT_CHANNEL + ? AGC_APP_IDENTIFIER + : `${AGC_APP_IDENTIFIER}.${channel}`, }); } diff --git a/apps/ai-game-creator-shell/scripts/check-config.mjs b/apps/ai-game-creator-shell/scripts/check-config.mjs index 448f91c87..31b7d52f4 100644 --- a/apps/ai-game-creator-shell/scripts/check-config.mjs +++ b/apps/ai-game-creator-shell/scripts/check-config.mjs @@ -25,7 +25,6 @@ execFileSync( import { AGC_APP_IDENTIFIER, - AGC_PRODUCT_NAME, resolveChannelInstallIdentity, } from './channel-identity.mjs'; import { @@ -1313,7 +1312,7 @@ if ( // 基线配置必须等于默认渠道的安装身份:默认渠道不能改身份,否则已发布客户端 // 的升级链路与既有安装目录都会断开。 const defaultChannelIdentity = resolveChannelInstallIdentity('dev'); -if (tauriConfig.productName !== AGC_PRODUCT_NAME) { +if (tauriConfig.productName !== defaultChannelIdentity.productName) { throw new Error('AI game creator shell productName drifted'); } @@ -1486,7 +1485,7 @@ if ( !Array.isArray(tauriConfig.app?.windows) || tauriConfig.app.windows.length !== 1 || tauriConfig.app.windows[0]?.label !== 'client' || - tauriConfig.app.windows[0]?.title !== '陶泥儿' || + tauriConfig.app.windows[0]?.title !== defaultChannelIdentity.productName || tauriConfig.app.windows[0]?.url !== 'index.html' ) { throw new Error( diff --git a/apps/ai-game-creator-shell/src-tauri/tauri.conf.json b/apps/ai-game-creator-shell/src-tauri/tauri.conf.json index 4771e3df3..cf24da0c5 100644 --- a/apps/ai-game-creator-shell/src-tauri/tauri.conf.json +++ b/apps/ai-game-creator-shell/src-tauri/tauri.conf.json @@ -1,6 +1,6 @@ { "$schema": "https://schema.tauri.app/config/2", - "productName": "陶泥儿", + "productName": "陶泥儿开发版", "version": "0.1.67", "identifier": "world.genarrative.ai-game-creator", "build": { @@ -14,7 +14,7 @@ "windows": [ { "label": "client", - "title": "陶泥儿", + "title": "陶泥儿开发版", "url": "index.html", "width": 1280, "height": 800, diff --git a/apps/ai-game-creator-shell/src/app/appMetadata.ts b/apps/ai-game-creator-shell/src/app/appMetadata.ts index 8ba27e8e9..d9e690fa7 100644 --- a/apps/ai-game-creator-shell/src/app/appMetadata.ts +++ b/apps/ai-game-creator-shell/src/app/appMetadata.ts @@ -1,5 +1,15 @@ import appPackage from '../../package.json'; +const DEFAULT_APP_NAME = '陶泥儿开发版'; + +/** + * 产品名由构建期注入;本地 Vite 开发没有注入时沿用 dev 渠道产品名。 + * + * 发布构建可通过 `VITE_AGC_PRODUCT_NAME` 注入渠道产品名,避免 UI 自己 + * 根据渠道推导名称,保证标题栏、关于页等前端展示与安装身份保持一致。 + */ +const injectedAppName = import.meta.env.VITE_AGC_PRODUCT_NAME?.trim(); + /** Product metadata shared by the client UI and release bundle. */ -export const APP_NAME = 'Genarrative AI Game Creator'; +export const APP_NAME = injectedAppName || DEFAULT_APP_NAME; export const APP_VERSION = appPackage.version; diff --git a/apps/ai-game-creator-shell/src/components/WindowChrome.tsx b/apps/ai-game-creator-shell/src/components/WindowChrome.tsx index 67d591ffb..e12c46cd8 100644 --- a/apps/ai-game-creator-shell/src/components/WindowChrome.tsx +++ b/apps/ai-game-creator-shell/src/components/WindowChrome.tsx @@ -9,6 +9,7 @@ import { } from 'react'; import brandIcon from '../../../../packages/shared/src/icons/taonier-product-ip.png'; +import { APP_NAME } from '../app/appMetadata'; import { appUpdateCheckEnabled } from '../app/featureFlags'; import { ActiveProjectRunsPanel } from '../features/app-shell/ActiveProjectRunsPanel'; import { subscribeTauriEvent } from '../services/tauriEventSubscription'; @@ -150,12 +151,15 @@ export function WindowChrome({ children }: WindowChromeProps) { {appUpdateCheckEnabled ? : null}
-
+
- 陶泥儿 + {APP_NAME} GameAgent
diff --git a/apps/ai-game-creator-shell/tests/appSurface/runtime-settings.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/runtime-settings.suite.ts index 9d8c95fb5..95cbf2b10 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/runtime-settings.suite.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/runtime-settings.suite.ts @@ -1,6 +1,6 @@ import fs from 'node:fs'; -import { APP_VERSION } from '../../src/app/appMetadata'; +import { APP_NAME, APP_VERSION } from '../../src/app/appMetadata'; import { repoPath } from '../repoPath'; import { act, @@ -348,7 +348,7 @@ export function registerRuntimeSettingsTests() { fireEvent.click(screen.getByRole('button', { name: /关于/ })); - expect(screen.getByText('Genarrative AI Game Creator')).not.toBeNull(); + expect(screen.getByText(APP_NAME)).not.toBeNull(); expect( screen.getByTestId('runtime-settings-app-logo').getAttribute('src'), ).toContain('taonier-product-ip.png'); diff --git a/docs/project-memory/plans/【实施计划】AGC渠道安装身份隔离-2026-09-21.md b/docs/project-memory/plans/【实施计划】AGC渠道安装身份隔离-2026-09-21.md index a8f468584..982d7cb08 100644 --- a/docs/project-memory/plans/【实施计划】AGC渠道安装身份隔离-2026-09-21.md +++ b/docs/project-memory/plans/【实施计划】AGC渠道安装身份隔离-2026-09-21.md @@ -47,3 +47,10 @@ - 风险:非默认渠道首次以新身份安装,老 `dev` 用户不会自动迁移本地数据。回滚点:渠道身份只影响非默认渠道构建,撤销该渠道的构建产物即可,仓库侧无数据迁移。 - 风险:窗口标题改为构建期产品名后,标题不再等于配置里的字面量。回滚点:去掉 `main.rs` 的标题覆盖调用,行为回到配置标题。 - 风险:ACL managed 识别放宽到前缀族。回滚点:`is_game_creator_packaged_app_data_leaf` 收紧回单一直线值,但非默认渠道的提权修复会重新失败关闭。 + +## 2026-09-23 追加:dev 渠道展示名统一 + +- `dev` 渠道继续复用 `world.genarrative.ai-game-creator`,保证既有安装、升级链和 AppData 路径不变;展示名统一为 `陶泥儿开发版`。 +- `channel-identity.mjs` 是展示名单一来源。发布构建将同一 `productName` 同时注入 Tauri 安装配置、原生窗口标题和 `VITE_AGC_PRODUCT_NAME`;React 自绘标题栏及关于/运行时配置展示从该注入值读取。 +- 因此 Windows NSIS 默认生成的快捷方式、开始菜单/卸载注册表展示名随 Tauri `productName` 变为 `陶泥儿开发版`;未新增自定义注册表或快捷方式实现。 +- 静态 Tauri 基线配置同步为 `陶泥儿开发版`,本地壳与正式 `dev` 包的显示名保持一致。