merge: master into codex/bark-battle

This commit is contained in:
kdletters
2026-05-19 17:04:32 +08:00
307 changed files with 40711 additions and 26022 deletions
File diff suppressed because it is too large Load Diff
@@ -387,16 +387,24 @@ vi.mock('../../services/rpg-entry/rpgProfileClient', () => ({
vi.mock('../ResolvedAssetImage', () => ({
ResolvedAssetImage: ({
src,
fallbackSrc,
alt,
className,
...rest
}: {
src?: string | null;
fallbackSrc?: string | null;
alt?: string;
className?: string;
}) =>
src ? (
<img src={src} alt={alt ?? ''} className={className} {...rest} />
<img
src={src}
data-fallback-src={fallbackSrc ?? undefined}
alt={alt ?? ''}
className={className}
{...rest}
/>
) : null,
}));
@@ -2901,6 +2909,36 @@ test('mobile discover recommend feed only rotates the card closest to screen cen
);
});
test('mobile discover recommend feed renders cover fallback for legacy browsers', async () => {
renderStatefulLoggedOutHomeView({
latestEntries: [
{
...puzzlePublicEntry,
coverImageSrc:
'/generated-puzzle-assets/puzzle-session-1/cover/image.png',
},
],
});
const discoverPanel = document.getElementById('platform-tab-panel-category');
if (!discoverPanel) {
throw new Error('缺少发现面板');
}
const card = within(discoverPanel).getByRole('button', { name: /奇幻拼图/u });
const cover = card.querySelector('.platform-public-work-card__cover');
const image = within(card).getByRole('img');
expect(cover).toBeTruthy();
expect(cover?.className).toContain('platform-public-work-card__cover');
expect(image.getAttribute('src')).toBe(
'/generated-puzzle-assets/puzzle-session-1/cover/image.png',
);
expect(image.getAttribute('data-fallback-src')).toBe(
'/creation-type-references/puzzle.webp',
);
});
test('mobile today channel only shows newly published works from today', async () => {
const user = userEvent.setup();
const now = new Date();
+10 -2
View File
@@ -31,6 +31,7 @@ import {
UserRound,
XCircle,
} from 'lucide-react';
import QRCode from 'qrcode';
import {
type ComponentType,
type CSSProperties,
@@ -42,7 +43,6 @@ import {
useRef,
useState,
} from 'react';
import QRCode from 'qrcode';
import communityQqQrImage from '../../../media/social-media-group/qq.png';
import communityWechatQrImage from '../../../media/social-media-group/wechat.png';
@@ -58,13 +58,13 @@ import type {
ProfileRechargeCenterResponse,
ProfileRechargeProduct,
ProfileReferralInviteCenterResponse,
WechatNativePayment,
ProfileSaveArchiveSummary,
ProfileTaskCenterResponse,
ProfileTaskItem,
ProfileWalletLedgerResponse,
RedeemProfileRewardCodeResponse,
WechatMiniProgramPayParams,
WechatNativePayment,
} from '../../../packages/shared/src/contracts/runtime';
import type { HydratedSavedGameSnapshot } from '../../persistence/runtimeSnapshotTypes';
import { buildPublicWorkDetailUrl } from '../../routing/appPageRoutes';
@@ -137,6 +137,7 @@ import {
resolvePlatformPublicWorkCode,
resolvePlatformWorldCoverImage,
resolvePlatformWorldCoverSlides,
resolvePlatformWorldFallbackCoverImage,
resolvePlatformWorldLeadPortrait,
} from './rpgEntryWorldPresentation';
@@ -392,11 +393,13 @@ function usePlatformDesktopLayout() {
function ResolvedAssetBackdrop({
src,
fallbackSrc,
alt,
className,
ariaHidden = false,
}: {
src?: string | null;
fallbackSrc?: string | null;
alt: string;
className: string;
ariaHidden?: boolean;
@@ -404,6 +407,7 @@ function ResolvedAssetBackdrop({
return (
<ResolvedAssetImage
src={src}
fallbackSrc={fallbackSrc}
alt={alt}
aria-hidden={ariaHidden}
className={className}
@@ -522,6 +526,7 @@ function WorldCard({
variant?: 'standard' | 'immersive';
}) {
const fallbackCoverImage = resolvePlatformWorldCoverImage(entry);
const fallbackAssetCoverImage = resolvePlatformWorldFallbackCoverImage(entry);
const coverSlides = useMemo(() => {
if (!enableCoverCarousel) {
return fallbackCoverImage
@@ -606,6 +611,7 @@ function WorldCard({
{coverImage ? (
<ResolvedAssetBackdrop
src={coverImage}
fallbackSrc={fallbackAssetCoverImage}
alt={entry.worldName}
className="absolute inset-0 h-full w-full object-cover"
/>
@@ -692,6 +698,7 @@ function RecommendCoverOnlyCard({
onClick: () => void;
}) {
const coverImage = resolvePlatformWorldCoverImage(entry);
const fallbackCoverImage = resolvePlatformWorldFallbackCoverImage(entry);
const displayName = formatPlatformWorkDisplayName(entry.worldName);
const typeLabel = describePublicGalleryCardKind(entry);
const authorName = entry.authorDisplayName.trim() || '玩家';
@@ -708,6 +715,7 @@ function RecommendCoverOnlyCard({
{coverImage ? (
<ResolvedAssetBackdrop
src={coverImage}
fallbackSrc={fallbackCoverImage}
alt={entry.worldName}
className="absolute inset-0 h-full w-full object-cover"
/>
@@ -13,7 +13,9 @@ import {
mapBabyObjectMatchDraftToPlatformGalleryCard,
mapVisualNovelWorkToPlatformGalleryCard,
type PlatformEdutainmentGalleryCard,
type PlatformPuzzleGalleryCard,
resolvePlatformPublicWorkCode,
resolvePlatformWorldFallbackCoverImage,
} from './rpgEntryWorldPresentation';
test('formatPlatformWorldTime formats backend seconds timestamp text as date', () => {
@@ -46,6 +48,32 @@ test('platform work display text limits names and tags by character count', () =
).toEqual(['超长机关', '星桥']);
});
test('platform public cards use play type reference images as cover fallback', () => {
const puzzleCard: PlatformPuzzleGalleryCard = {
sourceType: 'puzzle',
workId: 'puzzle-work-1',
profileId: 'puzzle-profile-1',
publicWorkCode: 'PZ-PUZZLE1',
ownerUserId: 'user-1',
authorDisplayName: '玩家',
worldName: '机关拼图',
subtitle: '拼图关卡',
summaryText: '公开作品',
coverImageSrc: '/generated-puzzle-assets/session/cover/image.png',
themeTags: ['拼图'],
playCount: 1,
remixCount: 0,
likeCount: 0,
visibility: 'published',
publishedAt: '2026-05-18T00:00:00.000Z',
updatedAt: '2026-05-18T00:00:00.000Z',
};
expect(resolvePlatformWorldFallbackCoverImage(puzzleCard)).toBe(
'/creation-type-references/puzzle.webp',
);
});
test('buildPuzzleWorkCoverSlides prefers each level formal image', () => {
const slides = buildPuzzleWorkCoverSlides({
workId: 'work-1',
@@ -446,6 +446,36 @@ export function resolvePlatformWorldCoverImage(entry: PlatformWorldCardLike) {
return '';
}
export function resolvePlatformWorldFallbackCoverImage(
entry: PlatformWorldCardLike,
) {
if (isPuzzleGalleryEntry(entry)) {
return '/creation-type-references/puzzle.webp';
}
if (isMatch3DGalleryEntry(entry)) {
return '/creation-type-references/match3d.webp';
}
if (isSquareHoleGalleryEntry(entry)) {
return '/creation-type-references/square-hole.webp';
}
if (isVisualNovelGalleryEntry(entry)) {
return '/creation-type-references/visual-novel.webp';
}
if (isBigFishGalleryEntry(entry)) {
return '/creation-type-references/big-fish.webp';
}
if (isEdutainmentGalleryEntry(entry)) {
return '/creation-type-references/creative-agent.webp';
}
return '/creation-type-references/rpg.webp';
}
export function resolvePlatformWorldCoverSlides(
entry: PlatformWorldCardLike,
): PlatformPuzzleCoverSlide[] {