diff --git a/apps/ai-game-creator-shell/src/styles.css b/apps/ai-game-creator-shell/src/styles.css index 200c4da8b..a088899a2 100644 --- a/apps/ai-game-creator-shell/src/styles.css +++ b/apps/ai-game-creator-shell/src/styles.css @@ -5059,6 +5059,10 @@ iframe.preview-frame { box-shadow: 0 0 0 3px var(--platform-input-focus-ring); } +.game-workbench-tabs.game-resource-sort-tabs button:focus-visible { + box-shadow: inset 0 0 0 3px var(--platform-input-focus-ring); +} + .game-workbench-tabs button.is-active, .game-workbench-view-actions button.is-active { border-color: var(--platform-button-primary-border); @@ -6241,6 +6245,16 @@ iframe.preview-frame { position: static; } +.game-workbench-editor-actions { + display: flex; + align-items: center; + gap: 8px; +} + +.game-workbench-editor-wallet .launcher-account-bar { + position: static; +} + .game-workbench-approval-trigger { display: inline-flex; align-items: center; diff --git a/apps/ai-game-creator-shell/src/view/project-development/index.tsx b/apps/ai-game-creator-shell/src/view/project-development/index.tsx index 79729a639..411bb5d18 100644 --- a/apps/ai-game-creator-shell/src/view/project-development/index.tsx +++ b/apps/ai-game-creator-shell/src/view/project-development/index.tsx @@ -3039,6 +3039,7 @@ export default function ProjectDevelopmentView({ resourceId={uiEditorRoute.resourceId} projectId={manifest.projectId} resourceLabel={uiEditorRoute.resourceLabel} + walletEntry={walletEntry} onBack={() => setUiEditorRoute(null)} /> diff --git a/apps/ai-game-creator-shell/src/view/ui-editor/index.tsx b/apps/ai-game-creator-shell/src/view/ui-editor/index.tsx index 9997a7ccd..f30878306 100644 --- a/apps/ai-game-creator-shell/src/view/ui-editor/index.tsx +++ b/apps/ai-game-creator-shell/src/view/ui-editor/index.tsx @@ -1,5 +1,5 @@ import { ChevronLeft } from 'lucide-react'; -import { useMemo, useState } from 'react'; +import { type ReactNode, useMemo, useState } from 'react'; import { ThemedModal } from '../../components/modal/ThemedModal'; import { @@ -27,6 +27,7 @@ export default function UiEditorPage({ resourceId, projectId, resourceLabel, + walletEntry, onBack, stateStore, }: { @@ -34,6 +35,7 @@ export default function UiEditorPage({ resourceId?: string; projectId?: string; resourceLabel?: string; + walletEntry?: ReactNode; onBack?: () => void; stateStore?: IUiDesignStateStore; }) { @@ -92,20 +94,25 @@ export default function UiEditorPage({ 返回资源 {resourceLabel ?? 'UI 设计'} - +
+ {walletEntry ? ( +
{walletEntry}
+ ) : null} + +
) : null} {session.save.saveError ? ( diff --git a/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts index 9507775bb..be543b01c 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts @@ -3429,7 +3429,13 @@ export function registerProjectWorkbenchFoundationTests() { ); expect(styles).toMatch( - /\.game-project-workbench\s*\{[^}]*width:\s*calc\(100vw - 72px\)[^}]*padding:\s*72px 8px 8px[^}]*overflow:\s*hidden/s, + /\.game-project-workbench\s*\{[^}]*width:\s*calc\(100vw - var\(--launcher-sidebar-width\)\)[^}]*padding:\s*0[^}]*overflow:\s*hidden/s, + ); + expect(styles).toMatch( + /\.launcher-main:has\(\.game-project-workbench\)\s*\{[^}]*padding-bottom:\s*0/s, + ); + expect(styles).toMatch( + /\.game-workbench-toolbar\s*\{[^}]*padding:\s*8px 12px[^}]*background:\s*transparent/s, ); expect(styles).toMatch( /@media \(min-width: 761px\)[\s\S]*?\.game-project-workbench\s*\{[^}]*grid-template-rows:\s*minmax\(0, 1fr\) auto[^}]*height:\s*100dvh/, @@ -3444,8 +3450,12 @@ export function registerProjectWorkbenchFoundationTests() { /\.game-workbench-chat \.project-runtime-summary\s*\{[^}]*position:\s*sticky[^}]*top:\s*-10px/s, ); expect(styles).toMatch( - /\.game-workbench-chat \.project-supervisor-composer\s*\{[^}]*grid-template-columns:\s*minmax\(0, 1fr\) 82px[^}]*z-index:\s*2[^}]*border-top:\s*1px solid var\(--platform-line-soft\)/s, + /\.game-workbench-chat \.project-supervisor-composer\s*\{[^}]*grid-template-columns:\s*minmax\(0, 1fr\) 82px[^}]*z-index:\s*2[^}]*padding-top:\s*8px[^}]*background:\s*transparent/s, ); + const composerRule = styles.match( + /\.game-workbench-chat \.project-supervisor-composer\s*\{([^}]*)\}/s, + ); + expect(composerRule?.[1]).not.toContain('border-top:'); expect(styles).toMatch( /\.game-workbench-chat \.project-supervisor-composer button\s*\{[^}]*min-height:\s*72px[^}]*white-space:\s*nowrap/s, ); @@ -3482,6 +3492,41 @@ export function registerProjectWorkbenchFoundationTests() { ); }); + it('keeps the wallet entry available when the workbench opens the UI editor', () => { + const styles = readFileSync( + resolve(process.cwd(), 'apps/ai-game-creator-shell/src/styles.css'), + 'utf8', + ); + const projectDevelopmentSource = readFileSync( + resolve( + process.cwd(), + 'apps/ai-game-creator-shell/src/view/project-development/index.tsx', + ), + 'utf8', + ); + + expect(styles).toMatch( + /\.launcher-main:has\(\.game-project-workbench\)\s*>\s*\.launcher-account-bar\s*\{[^}]*display:\s*none/s, + ); + expect(projectDevelopmentSource).toMatch( + //, + ); + expect(projectDevelopmentSource).toMatch( + /
\{walletEntry\}<\/div>/, + ); + }); + + it('keeps resource sort tab keyboard focus inside the clipped segmented control', () => { + const styles = readFileSync( + resolve(process.cwd(), 'apps/ai-game-creator-shell/src/styles.css'), + 'utf8', + ); + + expect(styles).toMatch( + /\.game-workbench-tabs\.game-resource-sort-tabs button:focus-visible\s*\{[^}]*box-shadow:\s*inset 0 0 0 3px var\(--platform-input-focus-ring\)/s, + ); + }); + it('keeps workbench chat bubbles aligned without shrinking process cards', () => { const styles = readFileSync( resolve(process.cwd(), 'apps/ai-game-creator-shell/src/styles.css'), @@ -9200,7 +9245,8 @@ export function registerProjectSupervisorSurfaceTests() { projectPath, }); let directTurnUpdateHandler: - ((event: { payload: Record }) => void) | null = null; + | ((event: { payload: Record }) => void) + | null = null; const listen = vi.fn( async ( eventName: string, @@ -10157,7 +10203,8 @@ export function registerProjectAgentStatusTests() { }, ); let runtimeUpdateHandler: - ((event: { payload: Record }) => void) | null = null; + | ((event: { payload: Record }) => void) + | null = null; const listen = vi.fn( async ( eventName: string, diff --git a/apps/ai-game-creator-shell/tests/uiEditorPage.test.ts b/apps/ai-game-creator-shell/tests/uiEditorPage.test.ts index d7510084e..45351babe 100644 --- a/apps/ai-game-creator-shell/tests/uiEditorPage.test.ts +++ b/apps/ai-game-creator-shell/tests/uiEditorPage.test.ts @@ -146,6 +146,34 @@ async function renderLoadedSession(state: State) { } describe('UiEditorPage', () => { + it('keeps the wallet entry in the resource editor header', async () => { + const stateStore: IUiDesignStateStore = { + load: vi.fn().mockResolvedValue(structuredClone(EMPTY_SNAPSHOT)), + save: vi.fn(), + }; + + render( + createElement(UiEditorPage, { + projectPath: '/tmp/ui-editor-wallet', + resourceId: 'ui-resource', + stateStore, + walletEntry: createElement( + 'button', + { type: 'button', 'aria-label': '打开账户资产' }, + '泥点', + ), + }), + ); + + const walletEntry = await screen.findByRole('button', { + name: '打开账户资产', + }); + expect(walletEntry.closest('header')).not.toBeNull(); + expect( + screen.getAllByRole('button', { name: '打开账户资产' }), + ).toHaveLength(1); + }); + it('cancels image preview scopes when the resource changes and on unmount', async () => { vi.mocked(invoke) .mockReset() diff --git a/docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md b/docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md index 1210b16e9..bbfe89dca 100644 --- a/docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md +++ b/docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md @@ -108,6 +108,9 @@ - 第一批共享 chrome 固定覆盖画布动作按钮、工具栏、工具分组和分隔符。按钮的默认、悬停、键盘焦点、选中、禁用和主次色语义由共享层表达;宿主只提供图标、文案、事件与业务禁用条件。 - 主站账号、钱包、服务端项目、云端素材库和生成面板仍留在网站宿主;客户端本地项目、manifest、Runtime、审批、草稿、生成与正式提交仍留在 Tauri 宿主。共享视觉组件不得读取这些业务事实。 - 客户端 UI 对齐采用“同视觉、同组件、保留四区布局”,不复制主站整页布局。资源总览、运行视图和 Supervisor 对话继续是 Game Agent 工作台独有语义。 +- 工作台四区作为同一应用工作面直接衔接,外层不使用卡片圆角或留白间距。中央主视窗与右侧 Supervisor 使用各自的平台区域底色,并以稳定的竖向分隔线明确边界;不通过外层圆角容器、阴影或区块间缝重新包装四区。 +- 右侧正式钱包入口属于客户端宿主 chrome,在普通项目工作台与 UI Editor 子路由中都必须持续可见、键盘可达且可操作,不得因进入素材编辑壳或隐藏全局 chrome 而丢失余额、充值和使用详情入口。 +- 资源总览的“资源依赖 / 资源类型”视图切换使用连通的分段按钮组,相邻选项共享边界并保持唯一选中语义。每个分段都必须有清晰的键盘焦点指示,焦点环不得被分段容器的圆角或 `overflow` 裁切。 - 右侧 Supervisor 对话中,用户消息使用右对齐、最大宽度受限的主题暖色气泡,assistant 消息保持左对齐;消息换行不得产生水平溢出,执行过程卡继续占满消息区可用宽度。气泡正文在 light / dark 平台主题下均须满足 WCAG AA 普通文本 `4.5:1` 对比度。 - 客户端正式产品仍只按最小 `1280×720` 横屏合同交付,并保留 `1280×800` 默认窗口与既有基线验收;更窄浏览器样式只负责不崩溃和开发兼容,不改成移动端创作工作台。 - 普通用户界面不默认展示内部错误码或本机绝对路径,也不展示 External Editor Base URL 或 Developer API Key;官方服务地址由客户端构建固定,远端编辑自动使用当前陶泥儿登录态。仅独立 standalone game-chat release 或显式高级自定义模式在隔离配置页维护 External v1 URL/Key,确需诊断的信息进入受控详情或开发模式。 @@ -567,6 +570,9 @@ type ProjectAgentMudPointAttribution = { 4. 主站现有图片编辑器的按钮名称、tool selection、hover/focus/pressed/disabled 行为与工具栏位置不因共享抽取回归;网站账号、钱包、素材库、生成和导出逻辑不下沉共享层。 5. 客户端生成、保存、取消、恢复、登录失效与余额不足只更换视觉承载,不改变 Host Port 调用、幂等身份、草稿 CAS、manifest/revision、Runtime 审批或钱包刷新语义。 6. 阶段一、二冻结合同并让主站消费共享 chrome;2026-08-06 的阶段三至五已经完成客户端中央区、Supervisor 和 Agent Dock 的正式换肤、定向测试与 `1280×800` 验收。独立 Vite 没有登录会话时继续显示真实登录门禁,不为视觉测试增加绕过入口。 +7. 普通工作台和 UI Editor 子路由均不显示工作面外层圆角或区块留白;中央区与右侧 Supervisor 的区域底色可辨识,竖向分隔连续、无错位。 +8. 右侧正式钱包入口在普通工作台和 UI Editor 子路由都始终可见、键盘可达并能打开余额、充值与使用详情;路由切换不得使入口消失或失去交互。 +9. “资源依赖 / 资源类型”以连通分段按钮呈现,点击与键盘操作均只保留一个选中项;使用 Tab 定位和键盘切换时焦点指示清晰、完整,不被容器边界或 `overflow` 裁切。 ## 8. 非目标