fix: reconcile architecture adjustment merge

This commit is contained in:
2026-06-07 00:57:23 +08:00
parent ce930ee5c3
commit 48ef19d518
20 changed files with 431 additions and 33 deletions

View File

@@ -219,6 +219,7 @@ import {
buildSquareHoleGenerationAnchorEntries,
buildWoodenFishGenerationAnchorEntries,
createMiniGameDraftGenerationState,
type MiniGameDraftGenerationKind,
type MiniGameDraftGenerationState,
resolveMiniGameDraftGenerationStartedAtMs,
} from '../../services/miniGameDraftGenerationProgress';
@@ -234,6 +235,7 @@ import {
buildSquareHolePublicWorkCode,
buildVisualNovelPublicWorkCode,
buildWoodenFishPublicWorkCode,
isSamePuzzleClearPublicWorkCode,
isSamePuzzlePublicWorkCode,
} from '../../services/publicWorkCode';
import {
@@ -373,7 +375,12 @@ import {
selectAdjacentPlatformRecommendEntry,
} from '../rpg-entry/rpgEntryPublicGalleryViewModel';
import {
isBigFishGalleryEntry,
isEdutainmentGalleryEntry,
isJumpHopGalleryEntry,
isPuzzleGalleryEntry,
isPuzzleClearGalleryEntry,
mapPuzzleClearWorkToPlatformGalleryCard,
mapPuzzleWorkToPlatformGalleryCard,
type PlatformPublicGalleryCard,
resolvePlatformPublicWorkCode,
@@ -415,6 +422,7 @@ import {
buildBigFishCreationUrlState,
buildJumpHopCreationUrlState,
buildMatch3DCreationUrlState,
buildPuzzleClearCreationUrlState,
buildPuzzleCreationUrlState,
buildPuzzleDraftRuntimeUrlState,
buildPuzzlePublishedRuntimeUrlState,
@@ -456,6 +464,7 @@ import {
buildPendingBigFishWorks,
buildPendingJumpHopWorks,
buildPendingMatch3DWorks,
buildPendingPuzzleClearWorks,
buildPendingPuzzleWorks,
buildPendingSquareHoleWorks,
buildPendingVisualNovelWorks,
@@ -557,6 +566,8 @@ import {
} from './platformMiniGameDraftPayloadModel';
import {
buildJumpHopPendingSession,
buildPuzzleClearPendingSession,
buildPuzzleClearSessionFromWorkDetail,
buildPuzzleRuntimeWorkFromSession,
buildSquareHoleProfileFromSession,
buildVisualNovelSessionFromWorkDetail,
@@ -752,7 +763,7 @@ async function resumePuzzleProfileSaveArchiveRaw(worldKey: string) {
);
}
const RECOMMEND_RUNTIME_BACKGROUND_AUTH_OPTIONS =
const RECOMMEND_RUNTIME_BACKGROUND_AUTH_OPTIONS: JumpHopRuntimeRequestOptions =
BACKGROUND_AUTH_REQUEST_OPTIONS;
const RECOMMEND_PUZZLE_BACKGROUND_AUTH_OPTIONS: JumpHopRuntimeRequestOptions =
RECOMMEND_RUNTIME_BACKGROUND_AUTH_OPTIONS;
@@ -778,6 +789,16 @@ function resolveCurrentRecommendRuntimeAuthPlan(
hasStoredAccessToken: Boolean(getStoredAccessToken()),
});
}
function shouldUseRecommendRuntimeGuestAuth(authUi: RecommendRuntimeAuthUi) {
return (
resolveCurrentRecommendRuntimeAuthPlan(authUi, {
embedded: true,
allowRuntimeGuestAuth: true,
}).requestKind === 'runtime-guest'
);
}
async function buildRecommendRuntimeOptionsFromAuthPlan(
plan: ReturnType<typeof resolvePlatformRecommendRuntimeAuthPlan>,
) {
@@ -797,6 +818,28 @@ async function buildRecommendRuntimeAuthOptions(
resolveCurrentRecommendRuntimeAuthPlan(authUi, { embedded }),
);
}
function resolveRecommendEntryShareStage(
entry: PlatformPublicGalleryCard,
): PublishShareModalPayload['stage'] {
if (isBigFishGalleryEntry(entry)) {
return 'big-fish-runtime';
}
if (isPuzzleGalleryEntry(entry)) {
return 'puzzle-gallery-detail';
}
return 'work-detail';
}
function pushPuzzleResultHistoryEntry(
session: PuzzleAgentSessionSnapshot | null,
) {
pushAppHistoryPath('/creation/puzzle/result');
writeCreationUrlState(buildPuzzleCreationUrlState(session));
}
const PUZZLE_DRAFT_GENERATION_POINT_COST = 2;
const MATCH3D_DRAFT_GENERATION_POINT_COST = 10;
const BARK_BATTLE_DRAFT_GENERATION_POINT_COST = 3;
@@ -2795,6 +2838,7 @@ export function PlatformEntryFlowShellImpl({
bigFishEntries: bigFishGalleryEntries,
match3dEntries: match3dGalleryEntries,
puzzleEntries: puzzleGalleryEntries,
puzzleClearEntries: puzzleClearGalleryEntries,
barkBattleGalleryEntries,
barkBattleWorks,
jumpHopEntries: jumpHopGalleryEntries,
@@ -12358,6 +12402,12 @@ export function PlatformEntryFlowShellImpl({
{ authMode: intent.authMode },
);
return;
case 'start-puzzle-clear':
setPublicWorkDetailError(null);
void startPuzzleClearRunFromProfile(intent.profileId, {
returnStage: intent.returnStage,
});
return;
case 'start-jump-hop':
setPublicWorkDetailError(null);
void startJumpHopRunFromProfile(intent.profileId, {
@@ -12491,6 +12541,8 @@ export function PlatformEntryFlowShellImpl({
setBigFishError(intent.errorMessage);
} else if (intent.errorTarget === 'puzzle') {
setPuzzleError(intent.errorMessage);
} else if (intent.errorTarget === 'puzzle-clear') {
setPuzzleClearError(intent.errorMessage);
} else if (intent.errorTarget === 'match3d') {
setMatch3DError(intent.errorMessage);
} else if (intent.errorTarget === 'square-hole') {
@@ -12514,6 +12566,12 @@ export function PlatformEntryFlowShellImpl({
{ embedded: intent.embedded },
);
break;
case 'start-puzzle-clear':
started = await startPuzzleClearRunFromProfile(intent.profileId, {
embedded: intent.embedded,
returnStage: intent.returnStage,
});
break;
case 'start-jump-hop':
started = await startJumpHopRunFromProfile(intent.profileId, {
embedded: intent.embedded,
@@ -13092,6 +13150,7 @@ export function PlatformEntryFlowShellImpl({
hasBigFishRun: Boolean(bigFishRun),
hasJumpHopRun: Boolean(jumpHopRun),
hasMatch3DRun: Boolean(match3dRun),
hasPuzzleClearRun: Boolean(puzzleClearRun),
hasSquareHoleRun: Boolean(squareHoleRun),
hasVisualNovelRun: Boolean(visualNovelRun),
hasWoodenFishRun: Boolean(woodenFishRun),
@@ -13114,6 +13173,7 @@ export function PlatformEntryFlowShellImpl({
hasBigFishRun: Boolean(bigFishRun),
hasJumpHopRun: Boolean(jumpHopRun),
hasMatch3DRun: Boolean(match3dRun),
hasPuzzleClearRun: Boolean(puzzleClearRun),
hasSquareHoleRun: Boolean(squareHoleRun),
hasVisualNovelRun: Boolean(visualNovelRun),
hasWoodenFishRun: Boolean(woodenFishRun),

View File

@@ -3,6 +3,10 @@ import type { BigFishSessionSnapshotResponse } from '../../../packages/shared/sr
import type { BabyObjectMatchDraft } from '../../../packages/shared/src/contracts/edutainmentBabyObject';
import type { Match3DAgentSessionSnapshot } from '../../../packages/shared/src/contracts/match3dAgent';
import type { PuzzleAgentSessionSnapshot } from '../../../packages/shared/src/contracts/puzzleAgentSession';
import type {
PuzzleClearSessionSnapshotResponse,
PuzzleClearWorkProfileResponse,
} from '../../../packages/shared/src/contracts/puzzleClear';
import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary';
import type { SquareHoleSessionSnapshot } from '../../../packages/shared/src/contracts/squareHoleAgent';
import type { VisualNovelAgentSessionSnapshot } from '../../../packages/shared/src/contracts/visualNovel';
@@ -393,6 +397,21 @@ export function buildJumpHopCreationUrlState(params: {
};
}
export function buildPuzzleClearCreationUrlState(params: {
session?: PuzzleClearSessionSnapshotResponse | null;
work?: PuzzleClearWorkProfileResponse | null;
}): CreationUrlState {
const sessionId = normalizeCreationUrlValue(params.session?.sessionId);
const profileId = normalizeCreationUrlValue(
params.work?.summary.profileId ?? params.session?.draft?.profileId,
);
return {
sessionId,
profileId,
workId: normalizeCreationUrlValue(params.work?.summary.workId ?? profileId),
};
}
export function buildWoodenFishCreationUrlState(params: {
session?: WoodenFishSessionSnapshotResponse | null;
work?: WoodenFishWorkProfileResponse | null;

View File

@@ -751,6 +751,7 @@ function buildJumpHopWork(
profileId: 'jump-hop-profile-base',
ownerUserId: 'user-1',
sourceSessionId: 'jump-hop-session-base',
themeText: '潮雾港口',
workTitle: '潮雾跳一跳',
workDescription: '潮雾港口跳一跳。',
themeTags: [],

View File

@@ -4,6 +4,7 @@ import type { CustomWorldWorkSummary } from '../../../packages/shared/src/contra
import type { BabyObjectMatchDraft } from '../../../packages/shared/src/contracts/edutainmentBabyObject';
import type { JumpHopWorkSummaryResponse } from '../../../packages/shared/src/contracts/jumpHop';
import type { Match3DWorkSummary } from '../../../packages/shared/src/contracts/match3dWorks';
import type { PuzzleClearWorkSummaryResponse } from '../../../packages/shared/src/contracts/puzzleClear';
import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary';
import type { SquareHoleWorkSummary } from '../../../packages/shared/src/contracts/squareHoleWorks';
import type { VisualNovelWorkSummary } from '../../../packages/shared/src/contracts/visualNovel';
@@ -1191,6 +1192,7 @@ export function buildPendingJumpHopWorks(
profileId: `jump-hop-profile-${sessionId}`,
ownerUserId: '',
sourceSessionId: sessionId,
themeText: state.title ?? '跳一跳草稿',
workTitle: '跳一跳草稿',
workDescription:
state.status === 'failed'
@@ -1210,6 +1212,48 @@ export function buildPendingJumpHopWorks(
});
}
export function buildPendingPuzzleClearWorks(
pending: Record<string, PendingDraftShelfState> | undefined,
existingItems: readonly PuzzleClearWorkSummaryResponse[],
): PuzzleClearWorkSummaryResponse[] {
if (!pending) {
return [];
}
return Object.entries(pending)
.filter(([sessionId]) =>
existingItems.every((item) => item.sourceSessionId !== sessionId),
)
.map(([sessionId, state]) => {
const generationStatus =
state.status === 'failed'
? 'failed'
: state.status === 'generating'
? 'generating'
: 'ready';
return {
runtimeKind: 'puzzle-clear',
workId: `puzzle-clear-work-${sessionId}`,
profileId: sessionId,
ownerUserId: '',
sourceSessionId: sessionId,
workTitle: '拼消消草稿',
workDescription:
state.status === 'failed'
? '拼消消草稿生成失败,可重新打开处理。'
: '正在生成拼消消草稿。',
themePrompt: '',
coverImageSrc: null,
publicationStatus: 'draft',
playCount: 0,
updatedAt: state.updatedAt,
publishedAt: null,
publishReady: false,
generationStatus,
};
});
}
export function buildPendingWoodenFishWorks(
pending: Record<string, PendingDraftShelfState> | undefined,
existingItems: readonly WoodenFishWorkSummaryResponse[],

View File

@@ -213,6 +213,7 @@ function buildJumpHopDraft(
templateId: 'jump-hop',
templateName: '跳一跳',
profileId: 'jump-hop-profile-1',
themeText: '草稿主题',
workTitle: '草稿跳一跳',
workDescription: '从草稿恢复。',
themeTags: ['草稿'],
@@ -236,6 +237,7 @@ function buildJumpHopPayload(
): JumpHopWorkspaceCreateRequest {
return {
templateId: 'jump-hop',
themeText: '表单主题',
workTitle: '表单跳一跳',
workDescription: '从表单提交。',
themeTags: ['表单'],

View File

@@ -76,7 +76,7 @@ export function buildPuzzleWorkUpdatePayloadFromDraft(
}
export function buildJumpHopDraftActionPayload(
actionType: 'compile-draft' | 'regenerate-character' | 'regenerate-tiles',
actionType: 'compile-draft' | 'regenerate-tiles',
input: {
payload?: JumpHopWorkspaceCreateRequest | null;
draft?: JumpHopSessionSnapshotResponse['draft'] | null;

View File

@@ -120,6 +120,7 @@ function buildJumpHopSummary(
profileId: 'jump-hop-profile-1',
ownerUserId: 'user-1',
sourceSessionId: ' jump-hop-session-1 ',
themeText: '云阶机关',
workTitle: '云阶跳跃',
workDescription: '越过云阶。',
themeTags: ['云阶'],
@@ -522,6 +523,7 @@ describe('platformMiniGameSessionMappingModel', () => {
templateId: 'jump-hop',
templateName: '跳一跳',
profileId: 'jump-hop-profile-1',
themeText: '云阶机关',
workTitle: '云阶跳跃',
workDescription: '越过云阶。',
themeTags: ['云阶'],

View File

@@ -1,4 +1,12 @@
import type { JumpHopSessionSnapshotResponse, JumpHopWorkSummaryResponse } from '../../../packages/shared/src/contracts/jumpHop';
import type {
JumpHopSessionSnapshotResponse,
JumpHopWorkSummaryResponse,
} from '../../../packages/shared/src/contracts/jumpHop';
import type {
PuzzleClearSessionSnapshotResponse,
PuzzleClearWorkProfileResponse,
PuzzleClearWorkSummaryResponse,
} from '../../../packages/shared/src/contracts/puzzleClear';
import type { PuzzleAgentSessionSnapshot } from '../../../packages/shared/src/contracts/puzzleAgentSession';
import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary';
import type { SquareHoleSessionSnapshot } from '../../../packages/shared/src/contracts/squareHoleAgent';
@@ -59,6 +67,54 @@ export function buildPuzzleRuntimeWorkFromSession(
};
}
export function buildPuzzleClearSessionFromWorkDetail(
work: PuzzleClearWorkProfileResponse,
fallbackItem?: PuzzleClearWorkSummaryResponse | null,
): PuzzleClearSessionSnapshotResponse {
const sessionId =
normalizeCreationUrlValue(work.summary.sourceSessionId) ??
normalizeCreationUrlValue(fallbackItem?.sourceSessionId) ??
work.summary.profileId;
return {
sessionId,
ownerUserId: work.summary.ownerUserId,
status: work.summary.generationStatus,
draft: work.draft,
createdAt: work.summary.updatedAt,
updatedAt: work.summary.updatedAt,
};
}
export function buildPuzzleClearPendingSession(
item: PuzzleClearWorkSummaryResponse,
): PuzzleClearSessionSnapshotResponse {
const sessionId =
normalizeCreationUrlValue(item.sourceSessionId) ?? item.profileId;
return {
sessionId,
ownerUserId: item.ownerUserId,
status: item.generationStatus,
draft: {
templateId: 'puzzle-clear',
templateName: '拼消消',
profileId: item.profileId,
workTitle: item.workTitle,
workDescription: item.workDescription,
themePrompt: item.themePrompt,
boardBackgroundPrompt: item.themePrompt,
generateBoardBackground: true,
boardBackgroundAsset: null,
cardBackImageSrc: null,
atlasAsset: null,
patternGroups: [],
cardAssets: [],
generationStatus: item.generationStatus,
},
createdAt: item.updatedAt,
updatedAt: item.updatedAt,
};
}
export function buildSquareHoleProfileFromSession(
session: SquareHoleSessionSnapshot | null,
): SquareHoleWorkProfile | null {
@@ -122,6 +178,7 @@ export function buildJumpHopPendingSession(
templateId: 'jump-hop',
templateName: '跳一跳',
profileId: item.profileId,
themeText: item.themeText,
workTitle: item.workTitle,
workDescription: item.workDescription,
themeTags: item.themeTags,

View File

@@ -303,6 +303,7 @@ function buildJumpHopCard(
profileId,
ownerUserId: 'user-1',
authorDisplayName: '测试作者',
themeText: '潮雾港',
workTitle: '潮雾跳一跳',
workDescription: '潮雾跳一跳说明。',
coverImageSrc: null,

View File

@@ -128,6 +128,7 @@ function buildJumpHopEntry(
profileId: 'jump-hop-profile',
ownerUserId: 'user-1',
authorDisplayName: '玩家',
themeText: '一路向前',
workTitle: '跳一跳',
workDescription: '一路向前。',
coverImageSrc: '/jump-hop-cover.png',
@@ -166,6 +167,13 @@ function buildTypedEntry(
switch (sourceType) {
case 'puzzle':
return { ...common, ...overrides, sourceType };
case 'puzzle-clear':
return {
...common,
...overrides,
sourceType,
themePrompt: '拼消消主题',
};
case 'big-fish':
return { ...common, ...overrides, sourceType };
case 'match3d':
@@ -769,6 +777,7 @@ test('platform public gallery flow builds feeds with visibility gates and bark b
bigFishEntries: [hiddenBigFish],
match3dEntries: [],
puzzleEntries: [],
puzzleClearEntries: [],
barkBattleGalleryEntries: [],
barkBattleWorks: [draftBarkFallback, publishedBarkFallback],
jumpHopEntries: [],
@@ -793,6 +802,7 @@ test('platform public gallery flow builds feeds with visibility gates and bark b
bigFishEntries: [hiddenBigFish],
match3dEntries: [],
puzzleEntries: [],
puzzleClearEntries: [],
barkBattleGalleryEntries: [
buildBarkBattleWork({
workId: 'gallery-bark',
@@ -828,6 +838,7 @@ test('platform public gallery flow preserves feed tie order and featured slice',
bigFishEntries: [],
match3dEntries: [],
puzzleEntries: [],
puzzleClearEntries: [],
barkBattleGalleryEntries: [],
barkBattleWorks: [
buildBarkBattleWork({
@@ -868,6 +879,7 @@ test('platform public gallery flow preserves feed tie order and featured slice',
bigFishEntries: [],
match3dEntries: [],
puzzleEntries: [],
puzzleClearEntries: [],
barkBattleGalleryEntries: [],
barkBattleWorks: [],
jumpHopEntries: [],

View File

@@ -8,12 +8,14 @@ import type { CustomWorldGalleryCard } from '../../../packages/shared/src/contra
import type { SquareHoleWorkSummary } from '../../../packages/shared/src/contracts/squareHoleWorks';
import type { VisualNovelWorkSummary } from '../../../packages/shared/src/contracts/visualNovel';
import type { WoodenFishGalleryCardResponse } from '../../../packages/shared/src/contracts/woodenFish';
import type { PuzzleClearGalleryCardResponse } from '../../services/puzzle-clear/puzzleClearClient';
import {
isBarkBattleGalleryEntry,
isBigFishGalleryEntry,
isEdutainmentGalleryEntry,
isJumpHopGalleryEntry,
isMatch3DGalleryEntry,
isPuzzleClearGalleryEntry,
isPuzzleGalleryEntry,
isSquareHoleGalleryEntry,
isVisualNovelGalleryEntry,
@@ -22,6 +24,7 @@ import {
mapBarkBattleWorkToPlatformGalleryCard,
mapBigFishWorkToPlatformGalleryCard,
mapJumpHopWorkToPlatformGalleryCard,
mapPuzzleClearWorkToPlatformGalleryCard,
mapPuzzleWorkToPlatformGalleryCard,
mapSquareHoleWorkToPlatformGalleryCard,
mapVisualNovelWorkToPlatformGalleryCard,
@@ -43,6 +46,7 @@ export type RecommendRuntimeKind =
| 'jump-hop'
| 'match3d'
| 'puzzle'
| 'puzzle-clear'
| 'square-hole'
| 'wooden-fish'
| 'visual-novel'
@@ -53,6 +57,7 @@ export type PlatformRecommendRuntimeStartErrorTarget =
| 'big-fish'
| 'match3d'
| 'puzzle'
| 'puzzle-clear'
| 'square-hole';
export type PlatformRecommendRuntimeStartIntent =
@@ -73,6 +78,12 @@ export type PlatformRecommendRuntimeStartIntent =
returnStage: 'platform';
embedded: true;
}
| {
type: 'start-puzzle-clear';
profileId: string;
returnStage: 'platform';
embedded: true;
}
| {
type: 'start-jump-hop';
profileId: string;
@@ -133,6 +144,7 @@ export type PlatformRecommendRuntimeReadyState = {
hasBigFishRun?: boolean;
hasJumpHopRun?: boolean;
hasMatch3DRun?: boolean;
hasPuzzleClearRun?: boolean;
hasSquareHoleRun?: boolean;
hasVisualNovelRun?: boolean;
hasWoodenFishRun?: boolean;
@@ -161,6 +173,7 @@ export type PlatformPublicGalleryFeedsInput = {
bigFishEntries: readonly BigFishWorkSummary[];
match3dEntries: readonly Match3DWorkSummary[];
puzzleEntries: readonly PuzzleWorkSummary[];
puzzleClearEntries: readonly PuzzleClearGalleryCardResponse[];
barkBattleGalleryEntries: readonly BarkBattleWorkSummary[];
barkBattleWorks: readonly BarkBattleWorkSummary[];
jumpHopEntries: readonly JumpHopGalleryCardResponse[];
@@ -194,21 +207,23 @@ export function getPlatformPublicGalleryEntryKey(
? 'big-fish'
: isPuzzleGalleryEntry(entry)
? 'puzzle'
: isJumpHopGalleryEntry(entry)
? 'jump-hop'
: isWoodenFishGalleryEntry(entry)
? 'wooden-fish'
: isMatch3DGalleryEntry(entry)
? 'match3d'
: isSquareHoleGalleryEntry(entry)
? 'square-hole'
: isVisualNovelGalleryEntry(entry)
? 'visual-novel'
: isBarkBattleGalleryEntry(entry)
? 'bark-battle'
: isEdutainmentGalleryEntry(entry)
? `edutainment:${entry.templateId}`
: 'rpg';
: isPuzzleClearGalleryEntry(entry)
? 'puzzle-clear'
: isJumpHopGalleryEntry(entry)
? 'jump-hop'
: isWoodenFishGalleryEntry(entry)
? 'wooden-fish'
: isMatch3DGalleryEntry(entry)
? 'match3d'
: isSquareHoleGalleryEntry(entry)
? 'square-hole'
: isVisualNovelGalleryEntry(entry)
? 'visual-novel'
: isBarkBattleGalleryEntry(entry)
? 'bark-battle'
: isEdutainmentGalleryEntry(entry)
? `edutainment:${entry.templateId}`
: 'rpg';
return `${kind}:${entry.ownerUserId}:${entry.profileId}`;
}
@@ -223,6 +238,10 @@ export function getPlatformRecommendRuntimeKind(
return 'puzzle';
}
if (isPuzzleClearGalleryEntry(entry)) {
return 'puzzle-clear';
}
if (isJumpHopGalleryEntry(entry)) {
return 'jump-hop';
}
@@ -297,6 +316,15 @@ export function resolvePlatformRecommendRuntimeStartIntent(
};
}
if (isPuzzleClearGalleryEntry(entry)) {
return {
type: 'start-puzzle-clear',
profileId: entry.profileId,
returnStage: 'platform',
embedded: true,
};
}
if (isJumpHopGalleryEntry(entry)) {
return {
type: 'start-jump-hop',
@@ -423,6 +451,9 @@ export function isPlatformRecommendRuntimeReadyForEntry(
state.puzzleRunCurrentLevelProfileId === entry.profileId
);
}
if (expectedKind === 'puzzle-clear') {
return Boolean(state.hasPuzzleClearRun);
}
if (expectedKind === 'square-hole') {
return Boolean(state.hasSquareHoleRun);
}
@@ -527,6 +558,7 @@ export function buildPlatformPublicGalleryFeeds(
...bigFishEntries,
...input.match3dEntries.map(mapMatch3DWorkToPublicWorkDetail),
...input.puzzleEntries.map(mapPuzzleWorkToPlatformGalleryCard),
...input.puzzleClearEntries.map(mapPuzzleClearWorkToPlatformGalleryCard),
...barkBattleGalleryEntries,
...input.jumpHopEntries.map(mapJumpHopWorkToPlatformGalleryCard),
...barkBattleFallbackEntries,
@@ -539,6 +571,7 @@ export function buildPlatformPublicGalleryFeeds(
...bigFishEntries,
...input.match3dEntries.map(mapMatch3DWorkToPublicWorkDetail),
...input.puzzleEntries.map(mapPuzzleWorkToPlatformGalleryCard),
...input.puzzleClearEntries.map(mapPuzzleClearWorkToPlatformGalleryCard),
...(barkBattleGalleryEntries.length > 0
? barkBattleGalleryEntries
: barkBattleFallbackEntries),

View File

@@ -6,6 +6,7 @@ import type { JumpHopGalleryCardResponse } from '../../../packages/shared/src/co
import type { Match3DWorkSummary } from '../../../packages/shared/src/contracts/match3dWorks';
import type { PuzzleRunSnapshot } from '../../../packages/shared/src/contracts/puzzleRuntimeSession';
import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary';
import type { PuzzleClearGalleryCardResponse } from '../../../packages/shared/src/contracts/puzzleClear';
import type {
CustomWorldGalleryCard,
CustomWorldLibraryEntry,
@@ -25,6 +26,7 @@ import {
mapBarkBattleWorkToPublicWorkDetail,
mapBigFishWorkToPublicWorkDetail,
mapJumpHopWorkToPublicWorkDetail,
mapPuzzleClearWorkToPublicWorkDetail,
mapPublicWorkDetailToBigFishWork,
mapPublicWorkDetailToPuzzleWork,
mapPublicWorkDetailToSquareHoleWork,
@@ -133,6 +135,13 @@ function buildTypedEntry<TSourceType extends PlatformGallerySourceType>(
...overrides,
sourceType,
});
case 'puzzle-clear':
return narrowTypedEntry<TSourceType>({
...common,
...overrides,
sourceType,
themePrompt: '拼消消主题',
});
case 'big-fish':
return narrowTypedEntry<TSourceType>({
...common,
@@ -324,6 +333,7 @@ function buildJumpHopGalleryCard(
profileId: 'jump-hop-profile',
ownerUserId: 'user-1',
authorDisplayName: '玩家',
themeText: '跳一跳',
workTitle: '跳一跳作品',
workDescription: '跳一跳摘要',
coverImageSrc: '/jump-hop-cover.png',
@@ -339,6 +349,31 @@ function buildJumpHopGalleryCard(
};
}
function buildPuzzleClearGalleryCard(
overrides: Partial<PuzzleClearGalleryCardResponse> = {},
): PuzzleClearGalleryCardResponse {
return {
runtimeKind: 'puzzle-clear',
publicWorkCode: 'PCLR-0001',
workId: 'puzzle-clear-work',
profileId: 'puzzle-clear-profile',
ownerUserId: 'user-1',
sourceSessionId: 'puzzle-clear-session',
authorDisplayName: '玩家',
workTitle: '拼消消作品',
workDescription: '拼消消摘要',
themePrompt: '水果',
coverImageSrc: '/puzzle-clear-cover.png',
publicationStatus: 'published',
playCount: 6,
updatedAt: '2026-06-01T01:00:00.000Z',
publishedAt: '2026-06-01T00:00:00.000Z',
publishReady: true,
generationStatus: 'ready',
...overrides,
};
}
function buildWoodenFishGalleryCard(
overrides: Partial<WoodenFishGalleryCardResponse> = {},
): WoodenFishGalleryCardResponse {
@@ -448,6 +483,7 @@ test('platform public work detail flow resolves detail kind for every play kind'
> = [
['big-fish', 'big-fish'],
['puzzle', 'puzzle'],
['puzzle-clear', 'puzzle-clear'],
['jump-hop', 'jump-hop'],
['wooden-fish', 'wooden-fish'],
['match3d', 'match3d'],
@@ -509,6 +545,13 @@ test('platform public work detail flow resolves open strategy', () => {
kind: 'edutainment',
},
],
[
buildTypedEntry('puzzle-clear'),
{
type: 'use-entry',
kind: 'puzzle-clear',
},
],
[
buildTypedEntry('puzzle'),
{
@@ -595,6 +638,14 @@ test('platform public work detail flow maps work summaries to detail entries', (
profileId: 'jump-hop-profile',
publicWorkCode: 'JH-0001',
});
expect(
mapPuzzleClearWorkToPublicWorkDetail(buildPuzzleClearGalleryCard()),
).toMatchObject({
sourceType: 'puzzle-clear',
workId: 'puzzle-clear-work',
profileId: 'puzzle-clear-profile',
publicWorkCode: 'PCLR-0001',
});
expect(
mapWoodenFishWorkToPublicWorkDetail(buildWoodenFishGalleryCard()),
).toMatchObject({
@@ -773,6 +824,12 @@ test('platform public work detail flow resolves like intent', () => {
type: 'like-puzzle',
profileId: 'puzzle-profile',
});
expect(
resolvePlatformPublicWorkLikeIntent(buildTypedEntry('puzzle-clear')),
).toEqual({
type: 'unsupported',
errorMessage: '拼消消点赞将在后续版本开放。',
});
expect(resolvePlatformPublicWorkLikeIntent(buildRpgEntry())).toEqual({
type: 'like-rpg-gallery',
ownerUserId: 'user-1',
@@ -826,6 +883,12 @@ test('platform public work detail flow resolves remix intent', () => {
profileId: 'puzzle-profile',
selectionStage: 'puzzle-result',
});
expect(
resolvePlatformPublicWorkRemixIntent(buildTypedEntry('puzzle-clear')),
).toEqual({
type: 'unsupported',
errorMessage: '拼消消作品改造将在后续版本开放。',
});
expect(resolvePlatformPublicWorkRemixIntent(buildRpgEntry())).toEqual({
type: 'remix-rpg-gallery',
ownerUserId: 'user-1',
@@ -1038,6 +1101,15 @@ test('platform public work detail flow resolves edit intent for unsupported and
type: 'blocked',
errorMessage: '这份跳一跳作品暂时请从作品架编辑。',
});
expect(
resolvePlatformPublicWorkEditIntent(
buildTypedEntry('puzzle-clear'),
buildEditIntentDeps(),
),
).toEqual({
type: 'blocked',
errorMessage: '这份拼消消作品暂时请从作品架编辑。',
});
expect(
resolvePlatformPublicWorkEditIntent(
buildTypedEntry('wooden-fish'),
@@ -1126,6 +1198,16 @@ test('platform public work detail flow resolves start intent for direct launches
profileId: 'jump-hop-profile',
returnStage: 'work-detail',
});
expect(
resolvePlatformPublicWorkStartIntent(
buildTypedEntry('puzzle-clear'),
buildStartIntentDeps(),
),
).toEqual({
type: 'start-puzzle-clear',
profileId: 'puzzle-clear-profile',
returnStage: 'work-detail',
});
expect(
resolvePlatformPublicWorkStartIntent(
buildTypedEntry('wooden-fish'),

View File

@@ -7,6 +7,10 @@ import type {
import type { Match3DWorkSummary } from '../../../packages/shared/src/contracts/match3dWorks';
import type { PuzzleRunSnapshot } from '../../../packages/shared/src/contracts/puzzleRuntimeSession';
import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary';
import type {
PuzzleClearGalleryCardResponse,
PuzzleClearWorkProfileResponse,
} from '../../../packages/shared/src/contracts/puzzleClear';
import type {
CustomWorldGalleryCard,
CustomWorldLibraryEntry,
@@ -25,6 +29,7 @@ import {
isEdutainmentGalleryEntry,
isJumpHopGalleryEntry,
isMatch3DGalleryEntry,
isPuzzleClearGalleryEntry,
isPuzzleGalleryEntry,
isSquareHoleGalleryEntry,
isVisualNovelGalleryEntry,
@@ -32,6 +37,7 @@ import {
mapBarkBattleWorkToPlatformGalleryCard,
mapBigFishWorkToPlatformGalleryCard,
mapJumpHopWorkToPlatformGalleryCard,
mapPuzzleClearWorkToPlatformGalleryCard,
mapPuzzleWorkToPlatformGalleryCard,
mapSquareHoleWorkToPlatformGalleryCard,
mapVisualNovelWorkToPlatformGalleryCard,
@@ -50,6 +56,7 @@ export type PlatformPublicWorkDetailKind =
| 'jump-hop'
| 'match3d'
| 'puzzle'
| 'puzzle-clear'
| 'rpg'
| 'square-hole'
| 'visual-novel'
@@ -199,6 +206,11 @@ export type PlatformPublicWorkStartIntent =
returnStage: 'work-detail';
authMode: 'isolated';
}
| {
type: 'start-puzzle-clear';
profileId: string;
returnStage: 'work-detail';
}
| {
type: 'start-jump-hop';
profileId: string;
@@ -325,6 +337,12 @@ export function mapJumpHopWorkToPublicWorkDetail(
return mapJumpHopWorkToPlatformGalleryCard(item);
}
export function mapPuzzleClearWorkToPublicWorkDetail(
item: PuzzleClearGalleryCardResponse | PuzzleClearWorkProfileResponse,
): PlatformPublicGalleryCard {
return mapPuzzleClearWorkToPlatformGalleryCard(item);
}
export function mapBarkBattleWorkToPublicWorkDetail(
item: BarkBattleWorkSummary,
): PlatformPublicGalleryCard {
@@ -512,6 +530,10 @@ export function getPlatformPublicWorkDetailKind(
return 'puzzle';
}
if (isPuzzleClearGalleryEntry(entry)) {
return 'puzzle-clear';
}
if (isJumpHopGalleryEntry(entry)) {
return 'jump-hop';
}
@@ -560,6 +582,13 @@ export function resolvePlatformPublicWorkDetailOpenStrategy(
};
}
if (isPuzzleClearGalleryEntry(entry)) {
return {
type: 'use-entry',
kind: 'puzzle-clear',
};
}
if (isJumpHopGalleryEntry(entry)) {
return {
type: 'load-jump-hop-detail',
@@ -653,6 +682,13 @@ export function resolvePlatformPublicWorkLikeIntent(
};
}
if (isPuzzleClearGalleryEntry(entry)) {
return {
type: 'unsupported',
errorMessage: '拼消消点赞将在后续版本开放。',
};
}
if (isBarkBattleGalleryEntry(entry)) {
return {
type: 'unsupported',
@@ -700,6 +736,13 @@ export function resolvePlatformPublicWorkRemixIntent(
};
}
if (isPuzzleClearGalleryEntry(entry)) {
return {
type: 'unsupported',
errorMessage: '拼消消作品改造将在后续版本开放。',
};
}
if (isMatch3DGalleryEntry(entry)) {
return {
type: 'unsupported',
@@ -833,6 +876,13 @@ export function resolvePlatformPublicWorkEditIntent(
};
}
if (isPuzzleClearGalleryEntry(entry)) {
return {
type: 'blocked',
errorMessage: '这份拼消消作品暂时请从作品架编辑。',
};
}
if (isWoodenFishGalleryEntry(entry)) {
return {
type: 'blocked',
@@ -944,6 +994,14 @@ export function resolvePlatformPublicWorkStartIntent(
};
}
if (isPuzzleClearGalleryEntry(entry)) {
return {
type: 'start-puzzle-clear',
profileId: entry.profileId,
returnStage: 'work-detail',
};
}
if (isJumpHopGalleryEntry(entry)) {
return {
type: 'start-jump-hop',

View File

@@ -48,6 +48,10 @@ const PROTECTED_DATA_LOSS_STABLE_STAGE_BY_STAGE = {
'puzzle-result': false,
'puzzle-gallery-detail': true,
'puzzle-runtime': false,
'puzzle-clear-workspace': true,
'puzzle-clear-generating': false,
'puzzle-clear-result': false,
'puzzle-clear-runtime': false,
'custom-world-generating': false,
'custom-world-result': false,
} as const satisfies Record<SelectionStage, boolean>;

View File

@@ -358,6 +358,7 @@ const {
}));
vi.mock('../../services/apiClient', () => ({
BACKGROUND_AUTH_REQUEST_OPTIONS: {},
refreshStoredAccessToken: mockRefreshStoredAccessToken,
}));
@@ -2670,7 +2671,7 @@ test('profile total play time card always uses hours', async () => {
});
const playTimeCard = screen.getByRole('button', {
name: //u,
name: //u,
});
expect(within(playTimeCard).getByText('1.5小时')).toBeTruthy();
@@ -2684,11 +2685,11 @@ test('profile played works card shows count unit', async () => {
});
const playedCard = screen.getByRole('button', {
name: /\s*1/u,
name: /\s*1/u,
});
expect(within(playedCard).getByText('1个')).toBeTruthy();
expect(within(playedCard).queryByText('已玩游戏数量')).toBeNull();
expect(within(playedCard).getByText('已玩游戏数量')).toBeTruthy();
await screen.findByText('1 / 1');
});
@@ -2700,8 +2701,12 @@ test('profile stats cards are centered without update timestamp', async () => {
const walletCard = screen.getByRole('button', {
name: /\s*0/u,
});
const playTimeCard = screen.getByRole('button', { name: /\s*0/u });
const playedCard = screen.getByRole('button', { name: /\s*0/u });
const playTimeCard = screen.getByRole('button', {
name: /\s*0/u,
});
const playedCard = screen.getByRole('button', {
name: /\s*0/u,
});
for (const card of [walletCard, playTimeCard, playedCard]) {
expect(card.className).toContain('platform-profile-stat-card');
@@ -2753,8 +2758,8 @@ test('mobile profile page matches the reference layout sections', async () => {
expect(statPanel.className).toContain('platform-profile-stats-panel');
expect(statPanel.querySelector('.platform-profile-stats-grid')).toBeTruthy();
expect(within(statPanel).getByRole('button', { name: /\s*70/u })).toBeTruthy();
expect(within(statPanel).getByRole('button', { name: /\s*0/u })).toBeTruthy();
expect(within(statPanel).getByRole('button', { name: /\s*0/u })).toBeTruthy();
expect(within(statPanel).getByRole('button', { name: /\s*0/u })).toBeTruthy();
expect(within(statPanel).getByRole('button', { name: /\s*0/u })).toBeTruthy();
expect(
within(statPanel).getByRole('button', { name: /\s*70/u }).className,
).toContain('platform-profile-stat-card');

View File

@@ -326,6 +326,15 @@ const WECHAT_PAY_CONFIRM_RETRY_DELAYS_MS = [800, 1600, 3000] as const;
const WECHAT_NATIVE_PAY_QR_IMAGE_SIZE = 180;
const PROFILE_QR_SCAN_INTERVAL_MS = 360;
function getDelayUntilNextProfileTaskReset(nowMs = Date.now()) {
const shiftedNow = nowMs + PROFILE_TASK_BEIJING_OFFSET_MS;
const nextDayStart =
Math.floor(shiftedNow / PROFILE_TASK_DAY_MS) * PROFILE_TASK_DAY_MS +
PROFILE_TASK_DAY_MS;
const nextResetAt = nextDayStart - PROFILE_TASK_BEIJING_OFFSET_MS;
return Math.max(PROFILE_TASK_MIN_RESET_DELAY_MS, nextResetAt - nowMs);
}
type ProfileReferralPanel = 'invite' | 'redeem' | 'community';
type ProfilePopupPanel = ProfileReferralPanel | 'saveArchives';
type BarcodeDetectorLike = {

View File

@@ -1018,6 +1018,9 @@ export function describePlatformPublicWorkKind(
if (isPuzzleGalleryEntry(entry)) {
return formatPlatformWorkDisplayTag('拼图');
}
if (isPuzzleClearGalleryEntry(entry)) {
return formatPlatformWorkDisplayTag('拼消消');
}
if (isMatch3DGalleryEntry(entry)) {
return formatPlatformWorkDisplayTag('抓大鹅');
}

View File

@@ -56,7 +56,7 @@ describe('jumpHopClient runtime requests', () => {
it('submits jump input with a generated client event id', async () => {
await submitJumpHopJump(
'run/1',
{ chargeMs: 320 },
{ dragDistance: 320 },
{ runtimeGuestToken: 'runtime-guest-token' },
);
@@ -69,7 +69,7 @@ describe('jumpHopClient runtime requests', () => {
Authorization: 'Bearer runtime-guest-token',
},
body: JSON.stringify({
chargeMs: 320,
dragDistance: 320,
clientEventId: 'jump-run/1-1780000000000',
}),
}),

View File

@@ -5,6 +5,7 @@ import type {
JumpHopGalleryCardResponse,
JumpHopGalleryDetailResponse,
JumpHopGalleryResponse,
JumpHopJumpRequest,
JumpHopLeaderboardResponse,
JumpHopRunResponse,
JumpHopRuntimeRunSnapshotResponse,
@@ -22,7 +23,11 @@ import {
requestJson,
} from '../apiClient';
import { createCreationAgentClient } from '../creation-agent';
import { type RuntimeGuestRequestOptions } from '../runtimeGuestAuth';
import {
buildRuntimeGuestAuthOptions,
buildRuntimeGuestHeaders,
type RuntimeGuestRequestOptions,
} from '../runtimeGuestAuth';
import { buildRuntimeApiPath, requestRuntimeJson } from '../runtimeRequest';
const JUMP_HOP_API_BASE = '/api/creation/jump-hop/sessions';
@@ -40,11 +45,10 @@ type JumpHopRuntimeMode = 'draft' | 'published';
type JumpHopStartRunOptions = JumpHopRuntimeRequestOptions & {
runtimeMode?: JumpHopRuntimeMode;
};
type JumpHopJumpPayload = {
dragDistance: number;
dragVectorX?: number;
dragVectorY?: number;
};
type JumpHopJumpPayload = Pick<
JumpHopJumpRequest,
'dragDistance' | 'dragVectorX' | 'dragVectorY'
>;
export type {
JumpHopActionRequest,

View File

@@ -4,10 +4,12 @@ import {
buildCustomWorldPublicWorkCode,
buildJumpHopPublicWorkCode,
buildMatch3DPublicWorkCode,
buildPuzzleClearPublicWorkCode,
buildWoodenFishPublicWorkCode,
isSameCustomWorldPublicWorkCode,
isSameJumpHopPublicWorkCode,
isSameMatch3DPublicWorkCode,
isSamePuzzleClearPublicWorkCode,
isSameWoodenFishPublicWorkCode,
} from './publicWorkCode';