修复资源界面测试并清理退役策划残留
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 5m57s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 5m26s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m10s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 5m21s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m15s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 4m32s
Project CI / Backend tests (pull_request) Successful in 9m36s
Project CI / Repository checks (pull_request) Successful in 9m13s
Project CI / Frontend tests (pull_request) Successful in 9m42s
Project CI / Native shell tests (pull_request) Successful in 11m55s
Project CI / AI game creator shell web tests (pull_request) Successful in 7m59s

更新资源卡片断言与 Lexical 提示词输入,恢复生成任务异步状态及轮询收尾验证。
删除旧策划别名和不可达错误映射,更新阶段审批注释。
保留旧项目无报错新开 Design 会话并继续的行为。
资源界面修复后完整界面测试 448 通过、17 跳过;审查清理后 Runtime 模型 27 项及 Design Agent 4 项测试通过,类型和配置检查通过。
This commit is contained in:
2026-09-18 11:46:34 +00:00
parent 8909b18a15
commit 43b7396909
4 changed files with 81 additions and 45 deletions
+8 -9
View File
@@ -562,7 +562,6 @@ export function App({
!supervisorChatOnly &&
!planningStartMode &&
!designAgentActive;
const planningLaneActive = designAgentActive;
const [devMode] = useState(() =>
projectSupervisorOnly ? false : isDeveloperMode(),
);
@@ -1641,7 +1640,7 @@ export function App({
if (
!canSubscribeTauriEvents() ||
directCodexProductRuntime ||
planningLaneActive
designAgentActive
) {
return;
}
@@ -1744,7 +1743,7 @@ export function App({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
directCodexProductRuntime,
planningLaneActive,
designAgentActive,
refreshManifest,
updateProjectSupervisorResponseStream,
updateProjectSupervisorRuntime,
@@ -1869,7 +1868,7 @@ export function App({
if (
!invoke ||
directCodexProductRuntime ||
planningLaneActive ||
designAgentActive ||
!nextProjectPath ||
!sessionId ||
!trackedRunId ||
@@ -1965,7 +1964,7 @@ export function App({
}, [
localProject?.projectPath,
directCodexProductRuntime,
planningLaneActive,
designAgentActive,
projectSupervisorSessionId,
projectSupervisorRuntime?.phase,
projectSupervisorRuntime?.runId,
@@ -1980,7 +1979,7 @@ export function App({
if (
!projectSupervisorOnly ||
directCodexProductRuntime ||
planningLaneActive ||
designAgentActive ||
!invoke ||
!nextProjectPath ||
!supervisorRunId
@@ -2061,7 +2060,7 @@ export function App({
}, [
localProject?.projectPath,
directCodexProductRuntime,
planningLaneActive,
designAgentActive,
projectSupervisorOnly,
projectSupervisorRuntime?.phase,
projectSupervisorRuntime?.runId,
@@ -2073,7 +2072,7 @@ export function App({
(!supervisorChatOnly &&
!(
projectSupervisorOnly &&
(directCodexProductRuntime || planningLaneActive)
(directCodexProductRuntime || designAgentActive)
)) ||
!supervisorChatShouldFollowLatestRef.current
) {
@@ -2090,7 +2089,7 @@ export function App({
projectSupervisorRuntimeError,
directThreadChat,
directCodexProductRuntime,
planningLaneActive,
designAgentActive,
projectSupervisorOnly,
supervisorChatOnly,
]);
@@ -2052,13 +2052,6 @@ export function projectRuntimeVisibleError(
) {
return `${subject} 保存运行记录失败,请检查项目目录后重试`;
}
if (
normalized.includes('planning_invalid') ||
normalized.includes('gdd 结构无效') ||
normalized.includes('策划输出格式')
) {
return `${subject} 输出格式不符合当前 GDD 结构,请重试`;
}
const containsInternalDiagnostics =
normalized.includes('agentllm.') ||
/(?:^|[\s:])kind=/.test(normalized) ||
@@ -2846,7 +2846,7 @@ export default function ProjectDevelopmentView({
[activeVersionId, manifest],
);
// 做方案在拿到第一个产物之前,左侧的资源区域是空的(文档 0 项、项目版本 0 项),而澄清
// 问答、决定卡和 GDD 审批全挤在右边那一列里。这段时间先把画布收掉让对话铺满,一旦有第
// 问答和阶段审批都在右侧对话列中。这段时间先把画布收掉让对话铺满,一旦有第
// 一个已登记资源就自动恢复双栏。用 CSS 收而不是不渲染:画布里的页签、缩放和选中状态都
// 留着,恢复双栏时不会被重置。
const conversationOnlyWorkbench = planningStartMode && resources.length === 0;
@@ -19,6 +19,10 @@ import { normalizeProjectResourceGraph } from '../../src/view/project-developmen
import { ResourceDependencyOverlay } from '../../src/view/project-development/ResourceDependencyOverlay';
import type { ProjectAgentResultSummary } from '../../src/view/project-development/resourceProjectionModel';
import { projectResourcesFromReadModels } from '../../src/view/project-development/resourceProjectionModel';
import {
generationPromptText,
typeGenerationPrompt,
} from '../resourceGenerationPromptTestUtils';
import {
act,
agentRuntimeUserInputRequest,
@@ -345,9 +349,9 @@ async function submitBottomToolbarPanel(
fireEvent.click(screen.getByRole('button', { name: label }));
}
const panel = await screen.findByRole('dialog', { name: label });
fireEvent.change(within(panel).getByLabelText('生成提示词'), {
target: { value: options.prompt },
});
// 提示词输入区是与聊天同一份 `@` 引用输入区(Lexical):jsdom 没有可用 Selection
// 浏览器输入事件不会落字,只能走编辑器更新(见 resourceGenerationPromptTestUtils)。
await typeGenerationPrompt(panel, options.prompt);
fireEvent.click(within(panel).getByRole('button', { name: label }));
// 成功路径由宿主卸载面板:等它消失,下一个入口才不会撞上残留的浮层。
await waitFor(() =>
@@ -2978,7 +2982,17 @@ export function registerProjectWorkbenchFoundationTests() {
await showResourcePage('角色与对象');
let heroDetailButton = await findResourceSelectButton('hero.png');
const heroCard = heroDetailButton.closest('.game-resource-card');
expect(heroCard?.textContent).not.toContain('hero.png');
// 卡面名称就是正式资源名(manifest `localPath` 的文件名,生成时来自 assetName、
// 重命名时同步改写),长名由 CSS 省略、完整名挂在实际命中指针的整卡选中按钮 `title` 上。
const heroName = heroCard?.querySelector('.game-resource-card-name');
expect(heroName?.textContent).toBe('hero.png');
expect(heroName?.getAttribute('data-resource-name')).toBe('hero.png');
expect(
heroCard
?.querySelector('.game-resource-card-select')
?.getAttribute('title'),
).toBe('hero.png');
// 卡面仍然只给"名称 + 类型",不铺完整路径与来源这类详细文本。
expect(heroCard?.textContent).not.toContain('assets/hero.png');
expect(heroCard?.textContent).not.toContain('Agent 生成');
// 角标显示的是**资源类型**(功能分类),等于它所在的画布栏目;不再是"图片"这类媒体类型。
@@ -3002,13 +3016,21 @@ export function registerProjectWorkbenchFoundationTests() {
});
await showResourcePage('待归类');
act(() => observer.triggerVisible());
const documentSummary = await screen.findByText(/这是安全的卡片正文摘要。/);
// 卡面正文按扩展名走文档分支(媒体类型),但角标是资源类型:`design-document` 不在
// canonical 目录里 → 落「待归类」,与它所在的栏目一致(这正是改前"标着文档、却在待归类栏"的分叉)。
// 文档卡卡面**不铺正文**:只显示居中图标 + 正式资源名,正文只在独立的文档详情浮层里读。
const documentCard = (
await findResourceSelectButton('design.md')
).closest<HTMLElement>('.game-resource-card');
expect(
documentSummary
.closest('.game-resource-card')
?.querySelector('[data-resource-type="待归类"]'),
documentCard?.querySelector('.game-resource-card-name')?.textContent,
).toBe('design.md');
expect(
documentCard?.querySelector('.game-resource-card-document-visual'),
).not.toBeNull();
expect(documentCard?.textContent).not.toContain('这是安全的卡片正文摘要。');
// 角标仍是资源类型:`design-document` 不在 canonical 目录里 → 落「待归类」,
// 与它所在的栏目一致(这正是改前"标着文档、却在待归类栏"的分叉)。
expect(
documentCard?.querySelector('[data-resource-type="待归类"]'),
).not.toBeNull();
await showResourcePage('角色与对象');
expect(
@@ -4249,9 +4271,9 @@ export function registerProjectWorkbenchFoundationTests() {
expect(screen.queryByRole('toolbar', { name: '图片工具栏' })).toBeNull();
});
expect(
screen
.queryAllByTitle('选中资源')
.some((card) => card.getAttribute('aria-pressed') === 'true'),
Array.from(
document.querySelectorAll<HTMLElement>('.game-resource-card-select'),
).some((card) => card.getAttribute('aria-pressed') === 'true'),
).toBe(false);
});
@@ -12212,9 +12234,7 @@ export function registerProjectAgentStatusTests() {
fireEvent.change(within(firstPanel).getByLabelText('素材名称'), {
target: { value: '第一条设计图' },
});
fireEvent.change(within(firstPanel).getByLabelText('生成提示词'), {
target: { value: '第一条界面' },
});
await typeGenerationPrompt(firstPanel, '第一条界面');
fireEvent.click(
within(firstPanel).getByRole('button', { name: '生成 UI 设计图' }),
);
@@ -12225,8 +12245,15 @@ export function registerProjectAgentStatusTests() {
// 2) 面板关掉之后任务仍在「生成任务」面板里,阶段文案来自后端记录。
const taskPanel = await screen.findByRole('region', { name: '生成任务' });
expect(within(taskPanel).getByText('第一条设计图')).not.toBeNull();
expect(within(taskPanel).getByText('正在生成。')).not.toBeNull();
expect(within(taskPanel).getByText('生成中')).not.toBeNull();
/**
* ****
* 2s
*
*/
await waitFor(() => {
expect(within(taskPanel).getByText('正在生成。')).not.toBeNull();
expect(within(taskPanel).getByText('生成中')).not.toBeNull();
});
// 非模态:没有全屏遮罩、没有 aria-modal。
expect(taskPanel.getAttribute('aria-modal')).toBeNull();
expect(document.querySelector('[aria-modal="true"]')).toBeNull();
@@ -12239,9 +12266,7 @@ export function registerProjectAgentStatusTests() {
fireEvent.change(within(secondPanel).getByLabelText('素材名称'), {
target: { value: '第二条设计图' },
});
fireEvent.change(within(secondPanel).getByLabelText('生成提示词'), {
target: { value: '第二条界面' },
});
await typeGenerationPrompt(secondPanel, '第二条界面');
fireEvent.click(
within(secondPanel).getByRole('button', { name: '生成 UI 设计图' }),
);
@@ -12286,6 +12311,28 @@ export function registerProjectAgentStatusTests() {
expect(
calls.filter((call) => call.command === 'get_local_game_manifest').length,
).toBeGreaterThanOrEqual(manifestReadsBefore + 2);
/**
* 5) 退 mock completed poll
* 2s
*
* 宿 `window.__TAURI__` afterEach
* mock/**** spy
* `does not open a preview before a local project is initialized`
* projectPath harness
*/
const ledgerReads = () =>
calls.filter(
(call) => call.command === 'list_local_project_asset_generations',
).length;
await act(async () => {
await new Promise((resolve) => setTimeout(resolve, 2_200));
});
const ledgerReadsAfterFirstQuietPeriod = ledgerReads();
await act(async () => {
await new Promise((resolve) => setTimeout(resolve, 2_200));
});
expect(ledgerReads()).toBe(ledgerReadsAfterFirstQuietPeriod);
}, 30_000);
/**
@@ -12799,9 +12846,7 @@ export function registerProjectAgentStatusTests() {
fireEvent.change(within(panel).getByLabelText('素材名称'), {
target: { value: '待提交设计图' },
});
fireEvent.change(within(panel).getByLabelText('生成提示词'), {
target: { value: '主界面与背包页' },
});
await typeGenerationPrompt(panel, '主界面与背包页');
return panel;
}
@@ -12848,10 +12893,9 @@ export function registerProjectAgentStatusTests() {
'项目权限策略拒绝执行:canvas.asset_generate',
),
);
expect(
(within(reopened).getByLabelText('生成提示词') as HTMLTextAreaElement)
.value,
).toBe('主界面与背包页');
await waitFor(() =>
expect(generationPromptText(reopened)).toBe('主界面与背包页'),
);
expect(
(within(reopened).getByLabelText('素材名称') as HTMLInputElement).value,
).toBe('待提交设计图');