Update Match3D/image-generation docs & code
Adds/updates documentation, assets and implementation for Match3D and puzzle image generation workflows. Key changes: decision logs and pitfalls updated to prefer VectorEngine Gemini for Match3D material sheets and to require edits (multipart) for 1:1 container reference images; guidance added for when to use APIMart vs VectorEngine. .env.example clarified APIMart/Responses config. Many new public assets and PPT visuals added. Code changes across frontend and backend: updated shared contracts, server-rs match3d/puzzle/image-generation handlers, VectorEngine/OpenAI image generation clients, and multiple React components/tests to handle UI/background/container image signing, edits workflow, and puzzle UI background resolution. Added src/services/puzzle-runtime/puzzleUiBackgroundSource.ts and related test updates. Includes notes about multipart HTTP/1.1 requirement and test/verification commands in docs.
This commit is contained in:
@@ -6,8 +6,8 @@ import { afterEach, expect, test, vi } from 'vitest';
|
||||
|
||||
import type { CustomWorldWorkSummary } from '../../../packages/shared/src/contracts/customWorldAgent';
|
||||
import type { SquareHoleWorkSummary } from '../../../packages/shared/src/contracts/squareHoleWorks';
|
||||
import { derivePlatformCreationTypes } from '../platform-entry/platformEntryCreationTypes';
|
||||
import type { CreationEntryConfig } from '../../services/creationEntryConfigService';
|
||||
import { derivePlatformCreationTypes } from '../platform-entry/platformEntryCreationTypes';
|
||||
import { CustomWorldCreationHub } from './CustomWorldCreationHub';
|
||||
|
||||
const noopCreateType = () => {};
|
||||
@@ -61,10 +61,10 @@ const testEntryConfig = {
|
||||
id: 'visual-novel',
|
||||
title: '视觉小说',
|
||||
subtitle: '分支叙事体验',
|
||||
badge: '可创建',
|
||||
badge: '敬请期待',
|
||||
imageSrc: '/creation-type-references/visual-novel.webp',
|
||||
visible: true,
|
||||
open: true,
|
||||
visible: false,
|
||||
open: false,
|
||||
sortOrder: 60,
|
||||
updatedAtMicros: 1,
|
||||
},
|
||||
@@ -443,7 +443,28 @@ test('creation hub shows RPG public work code from published library entry', ()
|
||||
expect(screen.queryByText('CW-00000001')).toBeNull();
|
||||
});
|
||||
|
||||
test('creation hub shows delete action for persisted rpg drafts', () => {
|
||||
test('creation hub hides persisted draft delete action behind swipe underlay', () => {
|
||||
const { container } = render(
|
||||
<CustomWorldCreationHub
|
||||
items={[{ ...baseDraftItem, profileId: 'profile-1' }]}
|
||||
loading={false}
|
||||
error={null}
|
||||
onRetry={() => {}}
|
||||
onCreateType={noopCreateType}
|
||||
onOpenDraft={() => {}}
|
||||
onEnterPublished={() => {}}
|
||||
onDeletePublished={() => {}}
|
||||
entryConfig={testEntryConfig}
|
||||
creationTypes={testCreationTypes}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(container.querySelector('.creation-work-card__swipe-underlay')).toBeTruthy();
|
||||
expect(screen.queryByRole('button', { name: '删除' })).toBeNull();
|
||||
});
|
||||
|
||||
test('creation hub reveals persisted draft delete action from keyboard', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(
|
||||
<CustomWorldCreationHub
|
||||
items={[{ ...baseDraftItem, profileId: 'profile-1' }]}
|
||||
@@ -459,10 +480,13 @@ test('creation hub shows delete action for persisted rpg drafts', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
screen.getByRole('button', { name: /继续完善《潮雾列岛》/u }).focus();
|
||||
await user.keyboard('{ArrowLeft}');
|
||||
|
||||
expect(screen.getByRole('button', { name: '删除' })).toBeTruthy();
|
||||
});
|
||||
|
||||
test('creation hub published work delete action is available beside share without opening card', async () => {
|
||||
test('creation hub published work delete action is revealed without opening card', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onDeletePuzzle = vi.fn();
|
||||
const onOpenPuzzleDetail = vi.fn();
|
||||
@@ -502,6 +526,12 @@ test('creation hub published work delete action is available beside share withou
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.queryByRole('button', { name: '删除' })).toBeNull();
|
||||
expect(screen.queryByRole('button', { name: '分享' })).toBeNull();
|
||||
|
||||
screen.getByRole('button', { name: /查看详情《待删拼图》/u }).focus();
|
||||
await user.keyboard('{ArrowLeft}');
|
||||
|
||||
expect(screen.getByRole('button', { name: '删除' })).toBeTruthy();
|
||||
expect(screen.getByRole('button', { name: '分享' })).toBeTruthy();
|
||||
|
||||
@@ -548,7 +578,7 @@ test('creation hub opens persisted rpg drafts by card click', async () => {
|
||||
expect(openedItems).toEqual([persistedDraft]);
|
||||
});
|
||||
|
||||
test('creation hub published share button copies share text without opening the card', async () => {
|
||||
test('creation hub published swipe share button copies share text without opening the card', async () => {
|
||||
const user = userEvent.setup();
|
||||
const writeText = vi.fn(async () => undefined);
|
||||
const onOpenPuzzleDetail = vi.fn();
|
||||
@@ -591,6 +621,8 @@ test('creation hub published share button copies share text without opening the
|
||||
/>,
|
||||
);
|
||||
|
||||
screen.getByRole('button', { name: /查看详情《沉钟拼图》/u }).focus();
|
||||
await user.keyboard('{ArrowLeft}');
|
||||
await user.click(screen.getByRole('button', { name: '分享' }));
|
||||
|
||||
expect(writeText).toHaveBeenCalledWith(
|
||||
|
||||
Reference in New Issue
Block a user