合并最新 master 到组件库收口分支
合入 origin/master 的 VectorEngine、后端主干和跳一跳更新 保留 PlatformUiKit 组件库收口提交并处理跳一跳运行态冲突 合并 Hermes 决策记录中的前端组件库、微信能力和后端流程决策
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1774,6 +1774,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
shouldRestoreCustomWorldAgentUiState(),
|
||||
);
|
||||
const handledInitialPublicWorkCodeRef = useRef<string | null>(null);
|
||||
const handledJumpHopRuntimeRestoreRef = useRef<string | null>(null);
|
||||
const selectionStageRef = useRef(selectionStage);
|
||||
const creationFlowReturnTargetRef =
|
||||
useRef<CreationFlowReturnTarget>('create');
|
||||
@@ -7494,6 +7495,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
profileId: string,
|
||||
options: {
|
||||
embedded?: boolean;
|
||||
preloadedWork?: JumpHopWorkProfileResponse | null;
|
||||
returnStage?: 'work-detail' | 'platform';
|
||||
} = {},
|
||||
) => {
|
||||
@@ -7526,7 +7528,11 @@ export function PlatformEntryFlowShellImpl({
|
||||
: null,
|
||||
);
|
||||
const [detail, runResponse] = await Promise.all([
|
||||
jumpHopClient.getWorkDetail(normalizedProfileId).catch(() => null),
|
||||
options.preloadedWork
|
||||
? Promise.resolve({ item: options.preloadedWork })
|
||||
: jumpHopClient
|
||||
.getWorkDetail(normalizedProfileId)
|
||||
.catch(() => null),
|
||||
jumpHopClient.startRun(normalizedProfileId, {
|
||||
...runtimeGuestOptions,
|
||||
runtimeMode: 'published',
|
||||
@@ -7558,6 +7564,78 @@ export function PlatformEntryFlowShellImpl({
|
||||
[authUi, setSelectionStage],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectionStage !== 'jump-hop-runtime' || jumpHopRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
const publicWorkCode = initialPublicWorkCode?.trim() ?? '';
|
||||
const restoreKey = publicWorkCode || '__jump-hop-runtime-empty__';
|
||||
if (handledJumpHopRuntimeRestoreRef.current === restoreKey) {
|
||||
return;
|
||||
}
|
||||
handledJumpHopRuntimeRestoreRef.current = restoreKey;
|
||||
|
||||
if (!publicWorkCode) {
|
||||
setJumpHopError(null);
|
||||
setJumpHopRuntimeRequestOptions(null);
|
||||
setJumpHopRuntimeReturnStage('platform');
|
||||
setSelectionStage('platform');
|
||||
pushAppHistoryPath('/');
|
||||
return;
|
||||
}
|
||||
|
||||
let cancelled = false;
|
||||
|
||||
const restoreJumpHopRuntime = async () => {
|
||||
setIsJumpHopBusy(true);
|
||||
setJumpHopError(null);
|
||||
try {
|
||||
const detail = await jumpHopClient.getGalleryDetail(publicWorkCode);
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
const profileId = detail.item.summary.profileId;
|
||||
const started = await startJumpHopRunFromProfile(profileId, {
|
||||
preloadedWork: detail.item,
|
||||
returnStage: 'work-detail',
|
||||
});
|
||||
if (!started && !cancelled) {
|
||||
setSelectionStage('platform');
|
||||
pushAppHistoryPath('/');
|
||||
}
|
||||
} catch (error) {
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
setJumpHopError(
|
||||
resolveRpgCreationErrorMessage(error, '恢复跳一跳玩法失败。'),
|
||||
);
|
||||
setJumpHopRun(null);
|
||||
setJumpHopRuntimeRequestOptions(null);
|
||||
setJumpHopRuntimeReturnStage('platform');
|
||||
setSelectionStage('platform');
|
||||
pushAppHistoryPath('/');
|
||||
} finally {
|
||||
if (!cancelled) {
|
||||
setIsJumpHopBusy(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void restoreJumpHopRuntime();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [
|
||||
initialPublicWorkCode,
|
||||
jumpHopRun,
|
||||
selectionStage,
|
||||
setSelectionStage,
|
||||
startJumpHopRunFromProfile,
|
||||
]);
|
||||
|
||||
const restartJumpHopRuntimeRun = useCallback(async () => {
|
||||
const runId = jumpHopRun?.runId;
|
||||
if (!runId) {
|
||||
@@ -13945,16 +14023,20 @@ export function PlatformEntryFlowShellImpl({
|
||||
|
||||
useEffect(() => {
|
||||
const publicWorkCode = initialPublicWorkCode?.trim();
|
||||
if (
|
||||
!publicWorkCode ||
|
||||
handledInitialPublicWorkCodeRef.current === publicWorkCode
|
||||
) {
|
||||
if (!publicWorkCode) {
|
||||
return;
|
||||
}
|
||||
if (selectionStage === 'jump-hop-runtime') {
|
||||
handledInitialPublicWorkCodeRef.current = publicWorkCode;
|
||||
return;
|
||||
}
|
||||
if (handledInitialPublicWorkCodeRef.current === publicWorkCode) {
|
||||
return;
|
||||
}
|
||||
|
||||
handledInitialPublicWorkCodeRef.current = publicWorkCode;
|
||||
void handlePublicCodeSearch(publicWorkCode);
|
||||
}, [handlePublicCodeSearch, initialPublicWorkCode]);
|
||||
}, [handlePublicCodeSearch, initialPublicWorkCode, selectionStage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectionStage === 'platform') {
|
||||
|
||||
@@ -1697,6 +1697,28 @@ function buildMockJumpHopWork(
|
||||
};
|
||||
}
|
||||
|
||||
function buildMockJumpHopRuntimeRun(
|
||||
work: JumpHopWorkProfileResponse,
|
||||
overrides: Partial<JumpHopRuntimeRunSnapshotResponse> = {},
|
||||
): JumpHopRuntimeRunSnapshotResponse {
|
||||
return {
|
||||
runId: 'jump-hop-run-1',
|
||||
profileId: work.summary.profileId,
|
||||
ownerUserId: work.summary.ownerUserId,
|
||||
status: 'playing',
|
||||
currentPlatformIndex: 0,
|
||||
successfulJumpCount: 0,
|
||||
durationMs: 0,
|
||||
score: 0,
|
||||
combo: 0,
|
||||
path: work.path,
|
||||
lastJump: null,
|
||||
startedAtMs: 1_779_999_000_000,
|
||||
finishedAtMs: null,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function buildMockBarkBattleWork(
|
||||
overrides: Partial<BarkBattleWorkSummary> = {},
|
||||
): BarkBattleWorkSummary {
|
||||
@@ -6938,6 +6960,89 @@ test('logged out public jump-hop detail starts runtime without requireAuth', asy
|
||||
expect(requireAuth).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('direct jump hop runtime route without work code returns platform home', async () => {
|
||||
window.history.replaceState(null, '', '/runtime/jump-hop');
|
||||
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(window.location.pathname).toBe('/');
|
||||
});
|
||||
expect(window.location.search).toBe('');
|
||||
expect(jumpHopClient.getGalleryDetail).not.toHaveBeenCalled();
|
||||
expect(jumpHopClient.startRun).not.toHaveBeenCalled();
|
||||
expect(await screen.findByRole('button', { name: '创作' })).toBeTruthy();
|
||||
});
|
||||
|
||||
test('direct jump hop runtime route with public work code starts published run', async () => {
|
||||
const publishedJumpHopWork = buildMockJumpHopWork({
|
||||
summary: {
|
||||
runtimeKind: 'jump-hop',
|
||||
workId: 'jump-hop-work-direct-1',
|
||||
profileId: 'jump-hop-profile-direct-12345678',
|
||||
ownerUserId: 'user-2',
|
||||
sourceSessionId: 'jump-hop-session-direct-1',
|
||||
themeText: '星星果园',
|
||||
workTitle: '星星果园跳一跳',
|
||||
workDescription: '沿着水果一路弹跳。',
|
||||
themeTags: ['果园', '星星'],
|
||||
difficulty: 'standard',
|
||||
stylePreset: 'paper-toy',
|
||||
coverImageSrc: null,
|
||||
publicationStatus: 'published',
|
||||
playCount: 8,
|
||||
updatedAt: '2026-06-07T10:00:00.000Z',
|
||||
publishedAt: '2026-06-07T10:00:00.000Z',
|
||||
publishReady: true,
|
||||
generationStatus: 'ready',
|
||||
},
|
||||
});
|
||||
const publishedJumpHopRun = buildMockJumpHopRuntimeRun(publishedJumpHopWork, {
|
||||
runId: 'jump-hop-run-direct-1',
|
||||
ownerUserId: '',
|
||||
});
|
||||
|
||||
window.history.replaceState(null, '', '/runtime/jump-hop?work=JH-12345678');
|
||||
vi.mocked(jumpHopClient.getGalleryDetail).mockResolvedValue({
|
||||
item: publishedJumpHopWork,
|
||||
});
|
||||
vi.mocked(jumpHopClient.startRun).mockResolvedValue({
|
||||
run: publishedJumpHopRun,
|
||||
});
|
||||
vi.mocked(jumpHopClient.getLeaderboard).mockResolvedValue({
|
||||
profileId: publishedJumpHopWork.summary.profileId,
|
||||
items: [],
|
||||
viewerBest: null,
|
||||
});
|
||||
|
||||
render(
|
||||
<TestWrapper
|
||||
authValue={createAuthValue({
|
||||
user: null,
|
||||
canAccessProtectedData: false,
|
||||
openLoginModal: () => {},
|
||||
requireAuth: vi.fn(),
|
||||
})}
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(jumpHopClient.getGalleryDetail).toHaveBeenCalledWith('JH-12345678');
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(jumpHopClient.startRun).toHaveBeenCalledWith(
|
||||
publishedJumpHopWork.summary.profileId,
|
||||
expect.objectContaining({
|
||||
runtimeGuestToken: 'runtime-guest-token',
|
||||
runtimeMode: 'published',
|
||||
}),
|
||||
);
|
||||
});
|
||||
expect(jumpHopClient.getWorkDetail).not.toHaveBeenCalled();
|
||||
expect(window.location.pathname).toBe('/runtime/jump-hop');
|
||||
expect(window.location.search).toContain('work=JH-12345678');
|
||||
});
|
||||
|
||||
test('owned public puzzle detail edits original draft instead of remixing', async () => {
|
||||
const user = userEvent.setup();
|
||||
const ownedPuzzleWork = {
|
||||
|
||||
@@ -65,8 +65,7 @@ test('jump hop workspace submits theme payload after required field is filled',
|
||||
difficulty: 'standard',
|
||||
stylePreset: 'minimal-blocks',
|
||||
characterPrompt: '内置默认 3D 角色',
|
||||
tilePrompt:
|
||||
'云朵跳台主题的正面30度视角主题物体图集,物体本身作为跳跃落点',
|
||||
tilePrompt: '云朵跳台主题的3D立方体主题身份方块包装图集',
|
||||
endMoodPrompt: null,
|
||||
});
|
||||
});
|
||||
@@ -90,13 +89,6 @@ test('jump hop workspace calls back when return button is clicked', async () =>
|
||||
expect(onBack).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('jump hop workspace theme label uses platform section label style', () => {
|
||||
render(<JumpHopCreationWorkspace onBack={() => {}} onSubmitted={() => {}} />);
|
||||
|
||||
expect(screen.getByText('主题').className).toContain('tracking-[0.18em]');
|
||||
expect(screen.getByLabelText('主题').className).toContain('bg-white/90');
|
||||
});
|
||||
|
||||
test('jump hop workspace can defer visible chrome to the unified creation page', () => {
|
||||
const { container } = render(
|
||||
<JumpHopCreationWorkspace
|
||||
|
||||
@@ -44,7 +44,7 @@ function buildJumpHopWorkspacePayload(
|
||||
difficulty: 'standard',
|
||||
stylePreset: 'minimal-blocks',
|
||||
characterPrompt: '内置默认 3D 角色',
|
||||
tilePrompt: `${themeText}主题的正面30度视角主题物体图集,物体本身作为跳跃落点`,
|
||||
tilePrompt: `${themeText}主题的3D立方体主题身份方块包装图集`,
|
||||
endMoodPrompt: null,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user