合并 master 并修复架构分支回归

合入 master 最新的认证、玩法契约与推荐页改动。

修复拼图草稿生成、推荐页下一关和公开详情访客试玩回归。

修复抓大鹅草稿试玩鉴权与首屏推荐详情测试入口。

补齐相关测试夹具、文档与团队记忆更新。
This commit is contained in:
2026-06-07 21:35:47 +08:00
80 changed files with 2627 additions and 511 deletions

View File

@@ -1,8 +1,9 @@
import type { PlatformCreationTypeId } from '../../packages/shared/src/contracts/playTypes';
import { requestJson } from './apiClient';
/** 后端下发的单个创作类型入口配置,前端只据此展示和分流。 */
export type CreationEntryTypeConfig = {
id: string;
id: PlatformCreationTypeId;
title: string;
subtitle: string;
badge: string;
@@ -27,7 +28,7 @@ export type UnifiedCreationField = {
/** 统一创作工作台契约,把入口类型映射到工作台、生成页和结果页阶段。 */
export type UnifiedCreationSpec = {
playId: string;
playId: PlatformCreationTypeId;
title: string;
workspaceStage: string;
generationStage: string;

View File

@@ -188,9 +188,16 @@ export function executeJumpHopCreationAction(
.then(normalizeJumpHopActionResponse);
}
export async function getJumpHopWorkDetail(profileId: string) {
export async function getJumpHopWorkDetail(
profileId: string,
options: { audience?: 'creation' | 'runtime' } = {},
) {
const base =
options.audience === 'creation'
? JUMP_HOP_WORKS_API_BASE
: `${JUMP_HOP_RUNTIME_API_BASE}/works`;
const response = await requestJson<JumpHopWorkDetailResponse>(
`${JUMP_HOP_RUNTIME_API_BASE}/works/${encodeURIComponent(profileId)}`,
`${base}/${encodeURIComponent(profileId)}`,
{ method: 'GET' },
'读取跳一跳作品详情失败',
);

View File

@@ -30,6 +30,7 @@ const leaderboardResponse: JumpHopLeaderboardResponse = {
{
rank: 1,
playerId: 'player-1',
displayName: '玩家一号',
successfulJumpCount: 10,
durationMs: 3210,
updatedAt: '2026-05-27T00:00:00Z',

View File

@@ -530,6 +530,44 @@ describe('miniGameDraftGenerationProgress', () => {
]);
});
test('jump hop generation anchors hide unused style preset fallback', () => {
const entries = buildJumpHopGenerationAnchorEntries({
sessionId: 'jump-hop-session-style-hidden',
ownerUserId: 'user-1',
status: 'generating',
draft: {
templateId: 'jump-hop',
templateName: '跳一跳',
profileId: 'jump-hop-profile-style-hidden',
themeText: '水果',
workTitle: '水果跳一跳',
workDescription: '水果主题跳一跳。',
themeTags: ['水果'],
difficulty: 'standard',
stylePreset: 'minimal-blocks',
characterPrompt: '内置默认 3D 角色',
tilePrompt: '',
endMoodPrompt: null,
characterAsset: null,
tileAtlasAsset: null,
tileAssets: [],
path: null,
coverComposite: null,
generationStatus: 'generating',
},
createdAt: '2026-06-06T10:00:00.000Z',
updatedAt: '2026-06-06T10:00:00.000Z',
});
expect(entries).toEqual([
{
id: 'jump-hop-theme',
label: '主题',
value: '水果',
},
]);
});
test('wooden fish draft generation exposes hit object, background and back button pipeline', () => {
const state = createMiniGameDraftGenerationState('wooden-fish');

View File

@@ -1163,7 +1163,7 @@ export function buildJumpHopGenerationAnchorEntries(
workTitle?: string;
themeText?: string;
characterPrompt?: string;
stylePreset?: string;
tilePrompt?: string;
} | null;
}
| null
@@ -1187,7 +1187,7 @@ export function buildJumpHopGenerationAnchorEntries(
value:
formPayload?.tilePrompt?.trim() ||
config?.tilePrompt?.trim() ||
draft?.stylePreset?.trim() ||
draft?.tilePrompt?.trim() ||
'',
},
];

View File

@@ -93,7 +93,7 @@ export async function dragPuzzlePieceOrGroup(
}
/**
* 进入推荐出的下一关。
* 进入当前 run 的下一关。
*/
export async function advancePuzzleNextLevel(
runId: string,
@@ -101,10 +101,8 @@ export async function advancePuzzleNextLevel(
options: PuzzleRuntimeRequestOptions = {},
) {
const targetProfileId = payload.targetProfileId?.trim() ?? '';
const preferSimilarWork = payload.preferSimilarWork === true;
const requestPayload = {
...(targetProfileId ? { targetProfileId } : {}),
...(preferSimilarWork ? { preferSimilarWork: true } : {}),
};
const hasRequestPayload = Object.keys(requestPayload).length > 0;
return requestRuntimeJson<PuzzleRunResponse>({

View File

@@ -130,10 +130,10 @@ describe('recommended runtime guest launch clients', () => {
},
);
it('puzzle next level can carry preferSimilarWork through the runtime guest request', async () => {
it('puzzle next level keeps the default current-run handoff without a request body', async () => {
await advancePuzzleNextLevel(
'run-puzzle-1',
{ preferSimilarWork: true },
{},
{ runtimeGuestToken: 'runtime-guest-token' },
);
@@ -144,11 +144,10 @@ describe('recommended runtime guest launch clients', () => {
method: 'POST',
headers: expect.objectContaining({
Authorization: 'Bearer runtime-guest-token',
'Content-Type': 'application/json',
}),
body: JSON.stringify({ preferSimilarWork: true }),
}),
);
expect(init.body).toBeUndefined();
expect(options).toEqual(
expect.objectContaining({
skipAuth: true,