Enforce Genarrative play-type SOP and update docs
Rewrite Genarrative play-type integration guidance across .codex and .hermes to define a platform-level SOP: default to form/image workbench, unify single-image asset slots (CreativeImageInputPanel), standardize series-material sheet->cut->transparent->OSS pipeline, and forbid copying legacy chat/agent workflows as the default. Add decision-log entry freezing the SOP and a pitfalls note warning against direct reuse of old play tools. Update CONTEXT.md and docs/README.md, add a new PRD file, and apply related small server-side changes (module-auth, spacetime-client mappers and runtime) to align back-end code with the new contracts and flows.
This commit is contained in:
@@ -390,9 +390,6 @@ describe('PuzzleResultView', () => {
|
||||
within(dialog).getByRole('button', { name: /生成画面/u }),
|
||||
).toBeTruthy();
|
||||
expect(within(dialog).getByText('消耗2泥点')).toBeTruthy();
|
||||
expect(
|
||||
within(dialog).getByText('等待时间可以制作更多关卡哦~'),
|
||||
).toBeTruthy();
|
||||
expect(within(dialog).getByText('画面图')).toBeTruthy();
|
||||
expect(
|
||||
within(dialog).queryByRole('button', { name: /关卡测试/u }),
|
||||
@@ -1013,7 +1010,7 @@ describe('PuzzleResultView', () => {
|
||||
fireEvent.change(screen.getByLabelText('拼图UI背景提示词'), {
|
||||
target: { value: '新拼图UI背景提示词' },
|
||||
});
|
||||
expect(screen.getByRole('button', { name: /生成UI背景 · 2泥点/u })).toBeTruthy();
|
||||
expect(screen.getByRole('button', { name: /生成UI背景.*2泥点/u })).toBeTruthy();
|
||||
fireEvent.click(screen.getByRole('button', { name: /生成UI背景/u }));
|
||||
const confirmDialog = screen.getByRole('dialog', {
|
||||
name: '确认消耗泥点',
|
||||
@@ -1352,6 +1349,184 @@ describe('PuzzleResultView', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('level image editor exposes entrance image editing controls without sharing UI background state', () => {
|
||||
const onExecuteAction = vi.fn();
|
||||
const session = createSession({
|
||||
draft: {
|
||||
...createSession().draft!,
|
||||
levels: [
|
||||
{
|
||||
...createSession().draft!.levels![0]!,
|
||||
pictureReference: '/generated-puzzle-assets/history/saved-reference.png',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
render(
|
||||
<PuzzleResultView
|
||||
session={session}
|
||||
onBack={() => {}}
|
||||
onExecuteAction={onExecuteAction}
|
||||
/>,
|
||||
);
|
||||
|
||||
openPuzzleLevelsTab();
|
||||
fireEvent.click(screen.getByText('雨夜猫街'));
|
||||
const dialog = screen.getByRole('dialog', { name: '关卡详情' });
|
||||
|
||||
expect(within(dialog).getByText('画面图')).toBeTruthy();
|
||||
expect(within(dialog).getByLabelText('上传参考图')).toBeTruthy();
|
||||
expect(within(dialog).getByRole('switch', { name: 'AI重绘' })).toHaveProperty(
|
||||
'checked',
|
||||
true,
|
||||
);
|
||||
expect(
|
||||
within(dialog).getByRole('button', { name: '选择历史图片' }),
|
||||
).toBeTruthy();
|
||||
|
||||
fireEvent.change(within(dialog).getByLabelText('画面AI重绘要求(提示词)'), {
|
||||
target: { value: '只重绘第一关猫街画面' },
|
||||
});
|
||||
fireEvent.click(within(dialog).getByRole('button', { name: /重新生成画面/u }));
|
||||
fireEvent.click(
|
||||
within(screen.getByRole('dialog', { name: '确认消耗泥点' })).getByRole(
|
||||
'button',
|
||||
{ name: '确定' },
|
||||
),
|
||||
);
|
||||
|
||||
expect(onExecuteAction).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: 'generate_puzzle_images',
|
||||
levelId: 'puzzle-level-1',
|
||||
promptText: '只重绘第一关猫街画面',
|
||||
aiRedraw: true,
|
||||
}),
|
||||
);
|
||||
expect(onExecuteAction).not.toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: 'generate_puzzle_ui_background',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('level image editor keeps AI redraw switch scoped to the level image action', () => {
|
||||
const onExecuteAction = vi.fn();
|
||||
const session = createSession({
|
||||
draft: {
|
||||
...createSession().draft!,
|
||||
levels: [
|
||||
{
|
||||
...createSession().draft!.levels![0]!,
|
||||
pictureReference: '/generated-puzzle-assets/history/saved-reference.png',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
render(
|
||||
<PuzzleResultView
|
||||
session={session}
|
||||
onBack={() => {}}
|
||||
onExecuteAction={onExecuteAction}
|
||||
/>,
|
||||
);
|
||||
|
||||
openPuzzleLevelsTab();
|
||||
fireEvent.click(screen.getByText('雨夜猫街'));
|
||||
const dialog = screen.getByRole('dialog', { name: '关卡详情' });
|
||||
|
||||
fireEvent.click(within(dialog).getByRole('switch', { name: 'AI重绘' }));
|
||||
fireEvent.click(within(dialog).getByRole('button', { name: /重新生成画面/u }));
|
||||
fireEvent.click(
|
||||
within(screen.getByRole('dialog', { name: '确认消耗泥点' })).getByRole(
|
||||
'button',
|
||||
{ name: '确定' },
|
||||
),
|
||||
);
|
||||
|
||||
expect(onExecuteAction).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: 'generate_puzzle_images',
|
||||
levelId: 'puzzle-level-1',
|
||||
aiRedraw: false,
|
||||
}),
|
||||
);
|
||||
expect(onExecuteAction).not.toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: 'generate_puzzle_ui_background',
|
||||
aiRedraw: false,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('level image editor hides AI redraw controls when only the formal image is shown', () => {
|
||||
const onExecuteAction = vi.fn();
|
||||
|
||||
render(
|
||||
<PuzzleResultView
|
||||
session={createSession()}
|
||||
onBack={() => {}}
|
||||
onExecuteAction={onExecuteAction}
|
||||
/>,
|
||||
);
|
||||
|
||||
openPuzzleLevelsTab();
|
||||
fireEvent.click(screen.getByText('雨夜猫街'));
|
||||
const dialog = screen.getByRole('dialog', { name: '关卡详情' });
|
||||
|
||||
expect(within(dialog).queryByRole('switch', { name: 'AI重绘' })).toBeNull();
|
||||
expect(within(dialog).getByLabelText('画面描述')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('UI background generator reuses common image input UI without sharing level image fields', () => {
|
||||
const onExecuteAction = vi.fn();
|
||||
|
||||
render(
|
||||
<PuzzleResultView
|
||||
session={createSession()}
|
||||
onBack={() => {}}
|
||||
onExecuteAction={onExecuteAction}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '素材配置' }));
|
||||
|
||||
expect(screen.getByText('UI背景预览')).toBeTruthy();
|
||||
expect(screen.getByLabelText('UI背景提示词')).toBeTruthy();
|
||||
expect(screen.queryByRole('switch', { name: 'AI重绘' })).toBeNull();
|
||||
expect(screen.queryByLabelText('上传拼图图片')).toBeNull();
|
||||
|
||||
fireEvent.change(screen.getByLabelText('UI背景提示词'), {
|
||||
target: { value: '独立的草稿UI背景提示词' },
|
||||
});
|
||||
fireEvent.click(screen.getByRole('button', { name: /生成UI背景/u }));
|
||||
fireEvent.click(
|
||||
within(screen.getByRole('dialog', { name: /确认消耗泥点/u })).getByRole(
|
||||
'button',
|
||||
{ name: '确定' },
|
||||
),
|
||||
);
|
||||
|
||||
expect(onExecuteAction).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: 'generate_puzzle_ui_background',
|
||||
promptText: '独立的草稿UI背景提示词',
|
||||
}),
|
||||
);
|
||||
const payload = onExecuteAction.mock.calls[0]![0];
|
||||
expect(payload).not.toHaveProperty('referenceImageSrc');
|
||||
expect(payload).not.toHaveProperty('aiRedraw');
|
||||
expect(JSON.parse(payload.levelsJson ?? '[]')).toEqual([
|
||||
expect.objectContaining({
|
||||
levelId: 'puzzle-level-1',
|
||||
pictureDescription: '屋檐下的猫与暖灯街角。',
|
||||
uiBackgroundPrompt: '独立的草稿UI背景提示词',
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
test('shows creative agent draft edit bar and submits the current draft', () => {
|
||||
const onSubmit = vi.fn();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user