From 36969726b48100a7e537983675082c2ad2c2d52b Mon Sep 17 00:00:00 2001 From: kdletters <61648117+kdletters@users.noreply.github.com> Date: Fri, 5 Jun 2026 23:40:12 +0800 Subject: [PATCH] fix(jump-hop): preserve themed runtime metadata --- server-rs/crates/api-server/src/jump_hop.rs | 17 +++- src/services/jump-hop/jumpHopClient.test.ts | 90 +++++++++++++++++++++ src/services/jump-hop/jumpHopClient.ts | 2 + 3 files changed, 108 insertions(+), 1 deletion(-) diff --git a/server-rs/crates/api-server/src/jump_hop.rs b/server-rs/crates/api-server/src/jump_hop.rs index 762f7c24..5c09a276 100644 --- a/server-rs/crates/api-server/src/jump_hop.rs +++ b/server-rs/crates/api-server/src/jump_hop.rs @@ -283,7 +283,10 @@ pub async fn start_jump_hop_run( ) -> Result, Response> { let Json(payload) = jump_hop_json(payload, &request_context, JUMP_HOP_RUNTIME_PROVIDER)?; ensure_non_empty(&request_context, &payload.profile_id, "profileId")?; - let is_draft_runtime = payload.runtime_mode.as_deref() == Some("draft"); + let is_draft_runtime = payload + .runtime_mode + .as_deref() + .is_some_and(is_jump_hop_draft_runtime_mode); let owner_user_id = principal.subject().to_string(); let principal_kind = principal.kind().as_str(); let run = state @@ -1240,6 +1243,10 @@ fn build_jump_hop_work_play_tracking_draft( WorkPlayTrackingDraft::runtime_principal("jump-hop", work_id, principal, source_route) } +fn is_jump_hop_draft_runtime_mode(runtime_mode: &str) -> bool { + runtime_mode.trim().eq_ignore_ascii_case("draft") +} + fn build_jump_hop_draft(payload: &JumpHopWorkspaceCreateRequest) -> JumpHopDraftResponse { let theme_text = normalize_theme_text(&payload.theme_text, &payload.work_title); JumpHopDraftResponse { @@ -1422,6 +1429,14 @@ fn current_utc_micros() -> i64 { mod tests { use super::*; + #[test] + fn jump_hop_draft_runtime_mode_detection_matches_client_normalization() { + assert!(is_jump_hop_draft_runtime_mode("draft")); + assert!(is_jump_hop_draft_runtime_mode(" DRAFT ")); + assert!(!is_jump_hop_draft_runtime_mode("published")); + assert!(!is_jump_hop_draft_runtime_mode("")); + } + #[test] fn jump_hop_tile_atlas_prompt_uses_dedicated_five_by_five_floor_layout() { let prompt = build_jump_hop_tile_atlas_prompt("森林冒险", "森林主题清爽游戏化立体感平台"); diff --git a/src/services/jump-hop/jumpHopClient.test.ts b/src/services/jump-hop/jumpHopClient.test.ts index 7f284fc0..a2d6907b 100644 --- a/src/services/jump-hop/jumpHopClient.test.ts +++ b/src/services/jump-hop/jumpHopClient.test.ts @@ -37,3 +37,93 @@ test('jump hop creation keeps image2 generation requests alive long enough', asy }), ); }); + +test('jump hop work detail preserves flattened back button asset', async () => { + const backButtonAsset = { + assetId: 'back-button-1', + imageSrc: '/generated-jump-hop-assets/back-button-1.png', + imageObjectKey: 'jump-hop/back-button-1.png', + assetObjectId: 'asset-object-back-button-1', + generationProvider: 'image2', + prompt: '主题返回按钮', + width: 1024, + height: 1024, + }; + const characterAsset = { + assetId: 'character-1', + imageSrc: 'builtin://jump-hop/default-character', + imageObjectKey: '', + assetObjectId: 'character-object-1', + generationProvider: 'builtin-three', + prompt: '内置默认角色', + width: 0, + height: 0, + }; + const draft = { + templateId: 'jump-hop', + templateName: '跳一跳', + profileId: 'profile-1', + themeText: '森林茶馆', + workTitle: '森林茶馆跳一跳', + workDescription: '森林茶馆主题', + themeTags: ['森林茶馆', '跳一跳'], + difficulty: 'standard', + stylePreset: 'minimal-blocks', + defaultCharacter: null, + characterPrompt: '内置默认角色', + tilePrompt: '森林茶馆主题地块', + endMoodPrompt: null, + characterAsset, + tileAtlasAsset: characterAsset, + tileAssets: [], + path: { + seed: 'profile-1', + difficulty: 'standard', + platforms: [], + scoring: { + perfectRadiusRatio: 0.24, + hitRadiusRatio: 0.52, + maxChargeMs: 1200, + minChargeMs: 80, + maxJumpDistance: 5, + }, + }, + coverComposite: null, + backButtonAsset: null, + generationStatus: 'ready', + }; + requestJsonMock.mockResolvedValue({ + item: { + runtimeKind: 'jump-hop', + workId: 'work-1', + profileId: 'profile-1', + ownerUserId: 'owner-1', + sourceSessionId: 'session-1', + themeText: '森林茶馆', + workTitle: '森林茶馆跳一跳', + workDescription: '森林茶馆主题', + themeTags: ['森林茶馆', '跳一跳'], + difficulty: 'standard', + stylePreset: 'minimal-blocks', + coverImageSrc: null, + publicationStatus: 'published', + playCount: 0, + updatedAt: '2026-06-05T00:00:00Z', + publishedAt: '2026-06-05T00:00:00Z', + publishReady: true, + generationStatus: 'ready', + draft, + path: draft.path, + defaultCharacter: null, + characterAsset, + tileAtlasAsset: characterAsset, + tileAssets: [], + backButtonAsset, + }, + }); + + const { jumpHopClient } = await import('./jumpHopClient'); + const response = await jumpHopClient.getWorkDetail('profile-1'); + + expect(response.item.backButtonAsset).toEqual(backButtonAsset); +}); diff --git a/src/services/jump-hop/jumpHopClient.ts b/src/services/jump-hop/jumpHopClient.ts index aeea825e..81cbc34b 100644 --- a/src/services/jump-hop/jumpHopClient.ts +++ b/src/services/jump-hop/jumpHopClient.ts @@ -136,6 +136,8 @@ function normalizeJumpHopWorkProfile( characterAsset: flattened.characterAsset, tileAtlasAsset: flattened.tileAtlasAsset, tileAssets: flattened.tileAssets, + backButtonAsset: + flattened.backButtonAsset ?? flattened.draft?.backButtonAsset ?? null, }; }