diff --git a/apps/ai-game-creator-shell/src/components/game-distribution/GameDistributionPublishPanel.tsx b/apps/ai-game-creator-shell/src/components/game-distribution/GameDistributionPublishPanel.tsx index 594e5fe71..895798b9a 100644 --- a/apps/ai-game-creator-shell/src/components/game-distribution/GameDistributionPublishPanel.tsx +++ b/apps/ai-game-creator-shell/src/components/game-distribution/GameDistributionPublishPanel.tsx @@ -254,13 +254,14 @@ export function GameDistributionPublishPanel({ GAME_DISTRIBUTION_DEFAULT_FORK_AUTHORIZATION, ); /** - * 作者是否已**主动确认**随本次发布公开整个项目目录(默认不勾选)。 + * 作者是否确认随本次发布公开整个项目目录(**默认已勾选**,作者可主动取消)。 * - * 产品口径:授权非「禁止共创」即允许他人改编,但工程源码的拒绝规则是黑名单——作者的 - * `docs/`、截图、设计稿、笔记等不在任何拒绝集内,会随包原样外发。所以这里不做「默认帮作者 - * 上传」,而是要求作者确认后才能上传;不确认则作品照常发布,只是退化为「只能被产物级改编」。 + * 产品口径:选择「允许共创」本身就意味着源码开放,不设独立开关——因此默认值必须与授权语义 + * 一致(默认勾选)。但工程源包的拒绝规则是黑名单,作者的 `docs/`、截图、设计稿、笔记等会随包 + * 原样外发,所以知情告知一字不减,并且作者可以随时取消:取消即本次不上传源码,作品照常发布, + * 只是退化为「只能被产物级改编」。 */ - const [projectBundleConfirmed, setProjectBundleConfirmed] = useState(false); + const [projectBundleConfirmed, setProjectBundleConfirmed] = useState(true); const [busy, setBusy] = useState(false); const [error, setError] = useState(''); const [result, setResult] = useState( @@ -960,7 +961,12 @@ export function GameDistributionPublishPanel({ value={value} checked={forkAuthorization === value} disabled={forkAuthorizationLocked} - onChange={() => setForkAuthorization(value)} + onChange={() => { + setForkAuthorization(value); + // 每次选择授权档位都回到「默认已勾选」:避免把上一次的取消状态带到 + // 新的档位选择上(禁止共创时不显示也不上传,值本身无意义)。 + setProjectBundleConfirmed(value !== 'forbidden'); + }} /> {GAME_DISTRIBUTION_FORK_AUTHORIZATION_LABELS[value]} @@ -999,8 +1005,9 @@ export function GameDistributionPublishPanel({ ) : null} {forkAuthorization !== 'forbidden' && !forkAuthorizationLocked ? (

- 不勾选则本次不上传工程源码:作品仍可正常发布,但他人只能拿到已构建成品试玩与 - 参考,不能直接重新发布。 + {'上面这项'} + 默认已勾选 + {'(平台按「允许共创即开放源码」的默认帮你勾上,不是替你做决定——你可以随时取消)。不勾选则本次不上传工程源码:作品仍可正常发布,但他人只能拿到已构建成品试玩与参考,不能直接重新发布。'}

) : null} diff --git a/apps/ai-game-creator-shell/tests/gameDistributionPublishPanel.test.tsx b/apps/ai-game-creator-shell/tests/gameDistributionPublishPanel.test.tsx index a851e74b3..4c43fb88a 100644 --- a/apps/ai-game-creator-shell/tests/gameDistributionPublishPanel.test.tsx +++ b/apps/ai-game-creator-shell/tests/gameDistributionPublishPanel.test.tsx @@ -382,7 +382,7 @@ describe('GameDistributionPublishPanel', () => { expect(args?.metadata?.forkAuthorization).toBe('full'); }); - test('授权开放后才出现源码公开确认,默认不勾选并如实告知后果', async () => { + test('授权开放后确认项出现且默认已勾选,并如实告知后果', async () => { installTauriInvoke(async () => undefined); vi.mocked(publishLocalProjectGame).mockResolvedValue({ gameId: 'game_1', @@ -400,8 +400,8 @@ describe('GameDistributionPublishPanel', () => { fireEvent.click(screen.getByRole('radio', { name: '允许非商用共创' })); const consent = await screen.findByRole('checkbox'); - // 知情同意:必须由作者主动确认,默认不勾选。 - expect(consent).toHaveProperty('checked', false); + // 产品口径:允许共创即开放源码,不设独立开关 → 默认已勾选(作者可主动取消)。 + expect(consent).toHaveProperty('checked', true); // 告知必须如实:整个项目目录会外发、只自动排除少数几类、且不可撤销。 expect(screen.getByText(/我已确认项目里没有不愿公开的内容/u)).not.toBeNull(); expect(screen.getByText(/整个项目目录/u)).not.toBeNull(); @@ -409,7 +409,10 @@ describe('GameDistributionPublishPanel', () => { expect(screen.getByText(/只会自动排除依赖目录、版本库、构建产物与常见凭据文件/u)).not.toBeNull(); expect(screen.getByText(/不可撤销/u)).not.toBeNull(); expect(screen.getByText(/无法收回别人已经下载的内容/u)).not.toBeNull(); - // 不勾选的后果也要在发布前就说清楚。 + // 默认值是平台替你勾上的,这一点要写出来,避免作者以为平台偷偷替他决定。 + expect(screen.getByText(/默认已勾选/u)).not.toBeNull(); + expect(screen.getByText(/你可以随时取消/u)).not.toBeNull(); + // 取消的后果也要在发布前就说清楚。 expect( screen.getByText(/不勾选则本次不上传工程源码/u), ).not.toBeNull(); @@ -418,7 +421,7 @@ describe('GameDistributionPublishPanel', () => { screen.getByText(/没有单独的源码可见性开关/u), ).not.toBeNull(); - // 未确认 → 明确告诉原生不要把工程源码交出去(作品照常发布)。 + // 默认勾选 → 不携带 includeProjectBundle,交给原生按「授权非禁止」执行上传。 await selectCover(); fireEvent.click(screen.getByRole('button', { name: '发布游戏' })); await waitFor(() => @@ -426,10 +429,10 @@ describe('GameDistributionPublishPanel', () => { ); expect( vi.mocked(publishLocalProjectGame).mock.calls[0]?.[0], - ).toMatchObject({ includeProjectBundle: false }); + ).not.toHaveProperty('includeProjectBundle'); }); - test('确认公开整个项目目录后才上传工程源码', async () => { + test('作者主动取消确认后,本次不上传工程源码(作品照常发布)', async () => { installTauriInvoke(async () => undefined); vi.mocked(publishLocalProjectGame).mockResolvedValue({ gameId: 'game_1', @@ -445,7 +448,7 @@ describe('GameDistributionPublishPanel', () => { fireEvent.click(screen.getByRole('radio', { name: '允许全开放共创' })); fireEvent.click(await screen.findByRole('checkbox')); await waitFor(() => - expect(screen.queryByRole('checkbox')).toHaveProperty('checked', true), + expect(screen.queryByRole('checkbox')).toHaveProperty('checked', false), ); await selectCover(); @@ -453,10 +456,33 @@ describe('GameDistributionPublishPanel', () => { await waitFor(() => expect(publishLocalProjectGame).toHaveBeenCalledTimes(1), ); - // 已确认时不带 includeProjectBundle:由原生按「授权非禁止」执行上传。 + // 这是「作者能退出」的证据:显式 false 表示源码不外发。 expect( vi.mocked(publishLocalProjectGame).mock.calls[0]?.[0], - ).not.toHaveProperty('includeProjectBundle'); + ).toMatchObject({ includeProjectBundle: false }); + }); + + test('重新选择授权档位会回到「默认已勾选」,切回禁止共创则不出现确认项', async () => { + installTauriInvoke(async () => undefined); + renderPanel(); + + // 取消后换一个档位:不应把上一次的取消带过去。 + fireEvent.click(screen.getByRole('radio', { name: '允许非商用共创' })); + fireEvent.click(await screen.findByRole('checkbox')); + await waitFor(() => + expect(screen.queryByRole('checkbox')).toHaveProperty('checked', false), + ); + fireEvent.click(screen.getByRole('radio', { name: '允许全开放共创' })); + await waitFor(() => + expect(screen.queryByRole('checkbox')).toHaveProperty('checked', true), + ); + + // 切回「禁止共创」:确认项消失(源码谁也取不走,不需要这个动作)。 + fireEvent.click(screen.getByRole('radio', { name: '禁止共创' })); + await waitFor(() => expect(screen.queryByRole('checkbox')).toBeNull()); + expect( + screen.getByRole('radio', { name: '禁止共创' }), + ).toHaveProperty('checked', true); }); test('工程源包未上传时成功页如实提示只能产物级改编', async () => {