From 0c0e7f6382512b92b6b4bef73c8ac06426cd8e8f Mon Sep 17 00:00:00 2001 From: menghao Date: Thu, 6 Aug 2026 13:30:52 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=20Game=20Agent=20=E7=94=BB?= =?UTF-8?q?=E5=B8=83=20UI=20=E5=AF=B9=E9=BD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 中央素材画布接入共享 chrome 与平台主题 token 统一 Supervisor、主视窗和 Agent Dock 的横屏布局 补齐按钮状态、非裁剪和 1280×800 布局测试 同步技术方案、PRD 与项目长期记忆 --- .../asset-canvas/AssetCanvasSurface.tsx | 354 +++++++++++------- .../asset-canvas/assetCanvasSurface.css | 229 ++++++----- apps/ai-game-creator-shell/src/styles.css | 262 +++++++++---- .../appSurface/project-development.suite.ts | 17 +- .../tests/assetCanvasSurface.test.tsx | 76 ++++ ...AI游戏创作】项目开发工作台PRD-2026-07-20.md | 6 +- .../shared-memory/decision-log.md | 6 +- docs/project-memory/shared-memory/pitfalls.md | 8 + ...案】AI游戏创作智能体App实施计划-2026-06-24.md | 8 + ...客户端素材创作无限画布阶段一合同-2026-08-05.md | 15 +- 10 files changed, 678 insertions(+), 303 deletions(-) diff --git a/apps/ai-game-creator-shell/src/features/asset-canvas/AssetCanvasSurface.tsx b/apps/ai-game-creator-shell/src/features/asset-canvas/AssetCanvasSurface.tsx index 17f1a78a2..b5cabdace 100644 --- a/apps/ai-game-creator-shell/src/features/asset-canvas/AssetCanvasSurface.tsx +++ b/apps/ai-game-creator-shell/src/features/asset-canvas/AssetCanvasSurface.tsx @@ -22,6 +22,10 @@ import { transformCanvasLayers, } from '@genarrative/image-canvas-core'; import { + CanvasChromeButton, + CanvasToolbar, + CanvasToolbarDivider, + CanvasToolbarGroup, CanvasViewport as SharedCanvasViewport, CanvasWorld, LayerRenderer, @@ -29,6 +33,22 @@ import { useCanvasHistory, ZoomControls, } from '@genarrative/image-canvas-react'; +import { + ArrowLeft, + Check, + ImagePlus, + Maximize2, + Minus, + Plus, + Redo2, + RotateCcw, + Save, + Sparkles, + Trash2, + Undo2, + Upload, + X, +} from 'lucide-react'; import { type ChangeEvent, type PointerEvent as ReactPointerEvent, @@ -339,6 +359,7 @@ export function AssetCanvasSurface({ const [documentVersion, setDocumentVersion] = useState(0); const [canvasSize, setCanvasSize] = useState({ width: 900, height: 640 }); const viewportElementRef = useRef(null); + const importInputRef = useRef(null); const layersRef = useRef(layers); const viewportRef = useRef(viewport); const backgroundRef = useRef(backgroundColor); @@ -1322,106 +1343,141 @@ export function AssetCanvasSurface({ data-session-id={sessionId} aria-label="素材创作无限画布" > -
- - - - - - - - setAssetName(event.target.value)} - disabled={lifecycle.kind === 'canvas.generating'} + onChange={(event) => void handleImport(event)} /> - setAssetKind(event.target.value)} - disabled={lifecycle.kind === 'canvas.generating'} - /> - -
{generationDialog ? ( @@ -1439,13 +1495,11 @@ export function AssetCanvasSurface({ {generationDialog === 'edit' ? 'AI 图片生成' : '确认图片生成'} - + /> {generationDialog === 'edit' ? (
@@ -1531,33 +1585,49 @@ export function AssetCanvasSurface({ ) : null}
- - +
) : (

将按 {generationAspectRatio}、{generationImageSize} 生成“ - {assetName}”,并在远端完成、下载和正式 manifest 提交全部成功后才显示为新资源。 -

-

- 本次会使用当前项目的图片生成扣费与幂等合同;关闭此面板不会自动确认。 + {assetName}”。图片保存到当前项目后会出现在资源列表中。

+

本次生成会按当前账号扣除泥点。

- - +
)} @@ -1582,9 +1652,13 @@ export function AssetCanvasSurface({ aria-label="图片生成进度" />

生成会由平台按当前账号扣除泥点,完成后自动保存到本地项目。

- + ) : null} @@ -1602,24 +1676,32 @@ export function AssetCanvasSurface({ {generationFailureTitle(lifecycle.code)}

{lifecycle.message}

- + {!lifecycle.reconciliationRequired ? ( <> - - + ) : null}
@@ -1774,25 +1856,29 @@ export function AssetCanvasSurface({ }} > {(actions) => ( -
- - - -
+ /> + )} {minimapModel ? ( diff --git a/apps/ai-game-creator-shell/src/features/asset-canvas/assetCanvasSurface.css b/apps/ai-game-creator-shell/src/features/asset-canvas/assetCanvasSurface.css index 717d6cfe2..a53f49c13 100644 --- a/apps/ai-game-creator-shell/src/features/asset-canvas/assetCanvasSurface.css +++ b/apps/ai-game-creator-shell/src/features/asset-canvas/assetCanvasSurface.css @@ -1,4 +1,10 @@ .asset-canvas-surface { + --image-canvas-brand-accent: var(--platform-button-primary-solid); + --image-canvas-brand-accent-strong: var(--platform-text-strong); + --image-canvas-brand-border-soft: var(--platform-icon-border); + --image-canvas-brand-border-strong: var(--platform-nav-active-border); + --image-canvas-brand-focus-ring: var(--platform-input-focus-ring); + --image-canvas-brand-soft: var(--platform-nav-active-fill); position: relative; display: grid; grid-template-rows: auto minmax(0, 1fr) auto; @@ -6,74 +12,87 @@ height: 100%; min-height: 0; overflow: hidden; - border-radius: 16px; - background: #e8e6e2; - color: #2d2926; + border-radius: inherit; + background: var(--platform-overlay-fill); + color: var(--platform-text-strong); } .asset-canvas-surface--loading { place-items: center; } -.asset-canvas-surface__toolbar { +.asset-canvas-surface__toolbar-shell { z-index: 4; - display: flex; + display: grid; min-width: 0; - align-items: center; gap: 8px; - padding: 10px 12px; - border-bottom: 1px solid rgba(63, 54, 48, 0.12); - background: rgba(252, 251, 249, 0.96); + padding: 8px 10px 9px; + border-bottom: 1px solid var(--platform-line-soft); + background: var(--platform-desktop-topbar-fill); } -.asset-canvas-surface button, -.asset-canvas-surface input, -.asset-canvas-surface select, -.asset-canvas-surface__import { - min-height: 34px; - border: 1px solid rgba(63, 54, 48, 0.18); - border-radius: 9px; - background: #fff; - color: inherit; - font: inherit; +.asset-canvas-surface__toolbar { + width: 100%; + max-width: 100%; + padding-block: 1px 3px; } -.asset-canvas-surface button, -.asset-canvas-surface__import { - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0 11px; - cursor: pointer; -} - -.asset-canvas-surface button:disabled { - cursor: default; - opacity: 0.45; -} - -.asset-canvas-surface input, -.asset-canvas-surface select { +.asset-canvas-surface__savebar { + display: grid; + grid-template-columns: + minmax(108px, 1.35fr) minmax(88px, 0.8fr) minmax(70px, auto) + auto; min-width: 0; + align-items: center; + gap: 7px; +} + +.asset-canvas-surface__savebar input, +.asset-canvas-surface__savebar select, +.asset-canvas-surface__generation-form select { + min-width: 0; + min-height: 34px; + border: 1px solid var(--platform-surface-border); + border-radius: 8px; + background: var(--platform-input-fill); + color: var(--platform-text-base); + font: inherit; padding: 0 9px; } -.asset-canvas-surface__import input { +.asset-canvas-surface__savebar input:focus-visible, +.asset-canvas-surface__savebar select:focus-visible, +.asset-canvas-surface__generation-form select:focus-visible, +.asset-canvas-surface__generation-form textarea:focus-visible { + border-color: var(--platform-surface-hover-border); + outline: none; + box-shadow: 0 0 0 3px var(--platform-input-focus-ring); +} + +.asset-canvas-surface__import-input { position: absolute; width: 1px; height: 1px; + margin: -1px; overflow: hidden; - clip: rect(0 0 0 0); + clip-path: inset(50%); + white-space: nowrap; } -.asset-canvas-surface__spacer { - flex: 1; +.asset-canvas-surface .asset-canvas-surface__primary-action, +.asset-canvas-surface .asset-canvas-surface__save { + border-color: var(--platform-button-primary-border); + background: var(--platform-button-primary-fill); + color: var(--platform-button-primary-text); + box-shadow: var(--platform-nav-active-shadow); } -.asset-canvas-surface__save { - border-color: #c7653d !important; - background: #c7653d !important; - color: #fff !important; +.asset-canvas-surface + .asset-canvas-surface__primary-action:not(:disabled):hover, +.asset-canvas-surface .asset-canvas-surface__save:not(:disabled):hover { + border-color: var(--platform-button-primary-border); + background: var(--platform-button-primary-fill); + color: var(--platform-button-primary-text); } .asset-canvas-surface[data-state='canvas.generating'] @@ -90,18 +109,25 @@ display: grid; place-items: center; padding: 16px; - background: rgba(35, 31, 28, 0.48); + background: color-mix( + in srgb, + var(--platform-text-strong) 36%, + transparent + ); + backdrop-filter: blur(5px); } .asset-canvas-surface__generation-dialog { display: grid; width: min(560px, 100%); - max-height: min(700px, calc(100dvh - 32px)); + max-height: calc(100% - 32px); + min-height: 0; overflow: auto; - border: 1px solid rgba(63, 54, 48, 0.16); + border: 1px solid var(--platform-modal-border); border-radius: 16px; - background: #fcfbf9; - box-shadow: 0 24px 80px rgba(35, 31, 28, 0.24); + background: var(--platform-modal-fill); + color: var(--platform-text-strong); + box-shadow: var(--platform-panel-shadow); } .asset-canvas-surface__generation-dialog > header, @@ -114,12 +140,7 @@ } .asset-canvas-surface__generation-dialog > header { - border-bottom: 1px solid rgba(63, 54, 48, 0.1); -} - -.asset-canvas-surface__generation-dialog > header button { - min-width: 34px; - padding: 0; + border-bottom: 1px solid var(--platform-line-soft); } .asset-canvas-surface__generation-form, @@ -132,18 +153,20 @@ .asset-canvas-surface__generation-form label { display: grid; gap: 6px; + color: var(--platform-text-base); font-size: 13px; + font-weight: 700; } .asset-canvas-surface__generation-form textarea { box-sizing: border-box; width: 100%; resize: vertical; - border: 1px solid rgba(63, 54, 48, 0.18); + border: 1px solid var(--platform-surface-border); border-radius: 10px; padding: 10px; - background: #fff; - color: inherit; + background: var(--platform-input-fill); + color: var(--platform-text-strong); font: inherit; } @@ -158,7 +181,7 @@ max-height: 180px; gap: 8px; overflow: auto; - border: 1px solid rgba(63, 54, 48, 0.12); + border: 1px solid var(--platform-subpanel-border); border-radius: 10px; padding: 10px; } @@ -166,6 +189,7 @@ .asset-canvas-surface__generation-form fieldset label { display: flex; align-items: center; + font-weight: 500; } .asset-canvas-surface__generation-form fieldset input { @@ -180,7 +204,11 @@ .asset-canvas-surface__viewport { grid-row: 2; min-height: 0; - background-image: radial-gradient(rgba(45, 41, 38, 0.12) 1px, transparent 1px); + background-color: var(--platform-neutral-bg); + background-image: radial-gradient( + var(--platform-line-soft) 1px, + transparent 1px + ); background-size: 18px 18px; } @@ -194,19 +222,27 @@ place-items: center; overflow: auto; padding: 20px; - background: rgba(45, 41, 38, 0.34); + background: color-mix( + in srgb, + var(--platform-text-strong) 30%, + transparent + ); backdrop-filter: blur(4px); } .asset-canvas-surface__operation-card { display: grid; width: min(440px, 100%); + max-height: 100%; + min-height: 0; gap: 14px; - border: 1px solid rgba(63, 54, 48, 0.14); + overflow: auto; + border: 1px solid var(--platform-modal-border); border-radius: 18px; padding: 22px; - background: #fcfbf9; - box-shadow: 0 20px 64px rgba(35, 31, 28, 0.2); + background: var(--platform-modal-fill); + color: var(--platform-text-strong); + box-shadow: var(--platform-panel-shadow); } .asset-canvas-surface__operation-card strong { @@ -215,8 +251,9 @@ .asset-canvas-surface__operation-card p { margin: 0; - color: #6f655f; + color: var(--platform-text-base); line-height: 1.55; + overflow-wrap: anywhere; } .asset-canvas-surface__operation-card progress { @@ -225,18 +262,18 @@ overflow: hidden; border: 0; border-radius: 999px; - accent-color: #c7653d; + accent-color: var(--platform-accent); } .asset-canvas-surface__operation-kicker { - color: #9c4c2d; + color: var(--platform-warm-text); font-size: 12px; font-weight: 700; letter-spacing: 0.08em; } .asset-canvas-surface__operation-card--failed { - border-color: rgba(164, 65, 45, 0.24); + border-color: var(--platform-button-danger-border); } .asset-canvas-surface__operation-actions { @@ -252,9 +289,9 @@ bottom: -8px; width: 16px; height: 16px; - border: 2px solid #fff; + border: 2px solid var(--platform-input-fill); border-radius: 50%; - background: #c7653d; + background: var(--platform-accent); cursor: nwse-resize; } @@ -268,14 +305,8 @@ } .asset-canvas-surface__zoom { - display: flex; + max-width: min(100%, 280px); justify-content: flex-end; - gap: 4px; -} - -.asset-canvas-surface__zoom button { - min-height: 30px; - background: rgba(255, 255, 255, 0.94); } .asset-canvas-surface .genarrative-image-canvas__minimap { @@ -283,28 +314,45 @@ height: 84px; padding: 0; overflow: hidden; - background: rgba(255, 255, 255, 0.94); + border: 1px solid var(--platform-surface-border); + background: var(--platform-input-fill); + box-shadow: var(--platform-nav-active-shadow); } .asset-canvas-surface .genarrative-image-canvas__minimap-layer { - background: rgba(199, 101, 61, 0.32); + background: var(--platform-warm-bg); } .asset-canvas-surface .genarrative-image-canvas__minimap-viewport { - border: 1px solid #c7653d; + border: 1px solid var(--platform-accent); } .asset-canvas-surface__status { z-index: 4; display: flex; min-height: 34px; + max-height: 96px; align-items: center; + flex-wrap: wrap; gap: 12px; + overflow-y: auto; padding: 8px 12px; - border-top: 1px solid rgba(63, 54, 48, 0.12); - background: rgba(252, 251, 249, 0.96); + border-top: 1px solid var(--platform-line-soft); + background: var(--platform-desktop-topbar-fill); + color: var(--platform-text-base); font-size: 12px; line-height: 1.4; + scrollbar-gutter: stable; +} + +.asset-canvas-surface__status > span { + min-width: 0; + overflow-wrap: anywhere; +} + +.asset-canvas-surface__status > span:first-child { + color: var(--platform-text-strong); + font-weight: 800; } @media (max-width: 760px) { @@ -314,13 +362,22 @@ border-radius: 0; } - .asset-canvas-surface__toolbar { - overflow-x: auto; + .asset-canvas-surface__toolbar-shell { + padding-inline: 8px; } - .asset-canvas-surface__toolbar input { - width: 112px; - flex: 0 0 auto; + .asset-canvas-surface__savebar { + grid-template-columns: minmax(96px, 1fr) minmax(72px, 0.75fr) 70px; + } + + .asset-canvas-surface__save { + grid-column: 1 / -1; + width: 100%; + } + + .asset-canvas-surface__viewport-tools { + right: 8px; + bottom: 52px; } .asset-canvas-surface__generation-options { diff --git a/apps/ai-game-creator-shell/src/styles.css b/apps/ai-game-creator-shell/src/styles.css index 16bb7812f..f71411098 100644 --- a/apps/ai-game-creator-shell/src/styles.css +++ b/apps/ai-game-creator-shell/src/styles.css @@ -3929,10 +3929,11 @@ iframe.preview-frame { .game-project-workbench { width: calc(100vw - 72px); max-width: none; - gap: 12px; - padding-top: 40px; - padding-bottom: 0; - color: #4b342c; + min-height: 0; + gap: 10px; + padding: 40px 8px 8px; + overflow: hidden; + color: var(--platform-text-base); } .launcher-main:has(.game-project-workbench) { @@ -3944,7 +3945,7 @@ iframe.preview-frame { z-index: 0; display: grid; grid-template-columns: minmax(0, 1fr) minmax(360px, 0.36fr); - gap: 12px; + gap: 10px; height: clamp(560px, calc(100dvh - 206px), 780px); min-width: 0; min-height: 0; @@ -3954,16 +3955,21 @@ iframe.preview-frame { .game-workbench-chat { min-width: 0; overflow: hidden; - border: 1px solid #ead7ce; + border: 1px solid var(--platform-desktop-panel-border); border-radius: 16px; - background: #fffdfa; - box-shadow: 0 14px 36px rgb(103 65 48 / 8%); + background: var(--platform-desktop-panel-fill); } .game-workbench-stage { position: relative; display: grid; grid-template-rows: auto auto minmax(0, 1fr); + box-shadow: var(--platform-panel-shadow); +} + +.game-workbench-chat { + border-color: var(--platform-line-soft); + box-shadow: var(--platform-nav-active-shadow); } .game-workbench-stage[data-resource-view-state^='resources.asset-canvas'] { @@ -3983,8 +3989,8 @@ iframe.preview-frame { gap: 12px; min-height: 48px; padding: 8px 12px; - border-bottom: 1px solid #f0dfd7; - background: #fff8f4; + border-bottom: 1px solid var(--platform-line-soft); + background: var(--platform-desktop-topbar-fill); } .game-workbench-tabs, @@ -3997,8 +4003,9 @@ iframe.preview-frame { .game-workbench-tabs { padding: 3px; + border: 1px solid var(--platform-subpanel-border); border-radius: 999px; - background: #f3ded3; + background: var(--platform-nav-fill); } .game-workbench-tabs button, @@ -4012,28 +4019,39 @@ iframe.preview-frame { border: 1px solid transparent; border-radius: 999px; background: transparent; - color: #735448; + color: var(--platform-button-ghost-text); font-size: 12px; + font-weight: 700; + white-space: nowrap; cursor: pointer; } +.game-workbench-tabs button:focus-visible, +.game-workbench-view-actions button:focus-visible, +.game-workbench-approval-trigger:focus-visible, +.game-agent-dock-more:focus-visible { + outline: none; + box-shadow: 0 0 0 3px var(--platform-input-focus-ring); +} + .game-workbench-tabs button.is-active, .game-workbench-view-actions button.is-active { - border-color: #d7845c; - background: #d7845c; - color: #fff; + border-color: var(--platform-button-primary-border); + background: var(--platform-button-primary-fill); + color: var(--platform-button-primary-text); } .game-workbench-tabs button:disabled, .game-workbench-tabs button[data-unavailable='true'] { - color: #a99086; + color: var(--platform-text-muted); cursor: not-allowed; opacity: 0.6; } .game-workbench-view-actions button { - border-color: #ead6cc; - background: #fff; + border-color: var(--platform-surface-border); + background: var(--platform-button-secondary-fill); + color: var(--platform-button-secondary-text); } .game-resource-reorder-status { @@ -4850,8 +4868,8 @@ iframe.preview-frame { align-items: center; gap: 9px; padding: 12px 14px; - border-bottom: 1px solid #f0dfd7; - background: #fff8f4; + border-bottom: 1px solid var(--platform-line-soft); + background: var(--platform-subpanel-fill); } .game-workbench-chat > header > div { @@ -4867,21 +4885,23 @@ iframe.preview-frame { min-height: 28px; margin-left: auto; padding: 0 9px; - border: 1px solid #e2c7ba; + border: 1px solid var(--platform-surface-border); border-radius: 999px; - background: #fff; - color: #9c5d42; + background: var(--platform-button-ghost-fill); + color: var(--platform-button-ghost-text); font-size: 10px; + font-weight: 700; + white-space: nowrap; cursor: pointer; } .game-workbench-chat > header strong { - color: #a95531; + color: var(--platform-text-strong); font-size: 13px; } .game-workbench-chat > header small { - color: #a08a80; + color: var(--platform-text-soft); font-size: 10px; } @@ -4890,8 +4910,8 @@ iframe.preview-frame { width: 30px; height: 30px; border-radius: 50%; - background: #d9794c; - color: #fff; + background: var(--platform-button-primary-fill); + color: var(--platform-button-primary-text); font-size: 12px; font-weight: 900; place-items: center; @@ -4908,24 +4928,28 @@ iframe.preview-frame { } .game-workbench-chat .project-supervisor-conversation { - grid-template-rows: minmax(120px, 1fr) minmax(0, auto) auto auto auto; + grid-template-rows: minmax(96px, 1fr) minmax(0, auto) auto auto auto; height: 100%; min-height: 0; overflow: hidden; } .game-workbench-chat .project-supervisor-message-list { - min-height: 120px; + min-height: 96px; max-height: none; overflow-y: auto; - border: 1px solid #f1e4de; - background: #fffdfb; + border: 1px solid var(--platform-subpanel-border); + background: var(--platform-input-fill); + scrollbar-gutter: stable; } .game-workbench-chat .agent-runtime-status { - max-height: clamp(190px, 30dvh, 300px); + max-height: clamp(120px, 24dvh, 240px); min-height: 0; overflow-y: auto; + border-color: var(--platform-subpanel-border); + background: var(--platform-neutral-bg); + color: var(--platform-text-strong); overscroll-behavior: contain; scrollbar-gutter: stable; } @@ -4936,8 +4960,8 @@ iframe.preview-frame { z-index: 3; gap: 5px; padding-bottom: 7px; - border-bottom: 1px solid #d8dde5; - background: #f8fafd; + border-bottom: 1px solid var(--platform-line-soft); + background: var(--platform-neutral-bg); } .project-runtime-empty-state { @@ -4946,12 +4970,12 @@ iframe.preview-frame { place-content: center; justify-items: center; gap: 10px; - color: #d9794c; + color: var(--platform-accent); text-align: center; } .project-runtime-empty-state strong { - color: #55443c; + color: var(--platform-text-strong); font-size: 13px; } @@ -4973,6 +4997,73 @@ iframe.preview-frame { .game-workbench-chat .project-runtime-professional-list article { gap: 3px; padding: 7px 8px; + border-color: var(--platform-subpanel-border); + background: var(--platform-input-fill); +} + +.game-workbench-chat .project-runtime-overview, +.game-workbench-chat + .agent-runtime-status + .project-runtime-pending-command { + background: var(--platform-warm-bg); +} + +.game-workbench-chat .project-runtime-overview > span, +.game-workbench-chat .project-runtime-plan-progress span, +.game-workbench-chat .project-runtime-professional-list article > header span, +.game-workbench-chat .project-runtime-professional-list article p, +.game-workbench-chat .project-supervisor-workspace-status { + color: var(--platform-text-base); +} + +.game-workbench-chat .project-runtime-plan-progress progress { + accent-color: var(--platform-accent); +} + +.game-workbench-chat + .agent-runtime-status + .project-runtime-professional-list { + border-top-color: var(--platform-line-soft); +} + +.game-workbench-chat .project-runtime-professional-actions button { + border-color: var(--platform-surface-border); + background: var(--platform-button-secondary-fill); + color: var(--platform-button-secondary-text); + white-space: nowrap; +} + +.game-workbench-chat .project-runtime-professional-actions button:last-child, +.game-workbench-chat .project-runtime-recovery > button { + border-color: var(--platform-button-primary-border); + background: var(--platform-button-primary-fill); + color: var(--platform-button-primary-text); +} + +.game-workbench-chat + .agent-runtime-status + .project-runtime-recovery { + border-color: var(--platform-button-danger-border); + background: var(--platform-button-danger-fill); +} + +.game-workbench-chat .project-runtime-recovery > span { + color: var(--platform-button-danger-text); +} + +.game-workbench-chat .project-runtime-recovery > span small { + color: var(--platform-text-base); +} + +.game-workbench-chat .message--user { + color: var(--platform-warm-text); +} + +.game-workbench-chat .message-action, +.game-workbench-chat .message-history-more { + border-color: var(--platform-surface-border); + background: var(--platform-button-secondary-fill); + color: var(--platform-button-secondary-text); } .game-workbench-chat .project-runtime-professional-list .project-runtime-error { @@ -4989,6 +5080,11 @@ iframe.preview-frame { .game-workbench-chat .project-supervisor-composer { grid-template-columns: minmax(0, 1fr) 82px; align-items: stretch; + position: relative; + z-index: 2; + padding-top: 8px; + border-top: 1px solid var(--platform-line-soft); + background: var(--platform-desktop-panel-fill); } .game-workbench-chat .project-supervisor-composer textarea { @@ -4996,12 +5092,26 @@ iframe.preview-frame { min-height: 72px; max-height: 72px; resize: none; + border-color: var(--platform-surface-border); + background: var(--platform-input-fill); + color: var(--platform-text-strong); +} + +.game-workbench-chat .project-supervisor-composer textarea:focus-visible { + border-color: var(--platform-surface-hover-border); + outline: none; + box-shadow: 0 0 0 3px var(--platform-input-focus-ring); } .game-workbench-chat .project-supervisor-composer button { width: auto; height: auto; min-height: 72px; + border-color: var(--platform-button-primary-border); + background: var(--platform-button-primary-fill); + color: var(--platform-button-primary-text); + font-weight: 800; + white-space: nowrap; } .game-agent-dock { @@ -5011,46 +5121,49 @@ iframe.preview-frame { display: flex; align-items: center; justify-content: center; - gap: 8px; + gap: 6px; overflow: visible; - min-height: 66px; - padding: 8px 12px; - border: 1px solid #ead7ce; - border-radius: 16px; - background: #f7e6dc; - box-shadow: 0 12px 28px rgb(103 65 48 / 10%); + min-height: 58px; + padding: 6px 10px; + border: 1px solid var(--platform-line-soft); + border-radius: 14px; + background: var(--platform-subpanel-fill); + box-shadow: var(--platform-nav-active-shadow); } .game-agent-dock-more { flex: 0 0 auto; - min-height: 40px; + min-height: 38px; padding: 0 12px; - border: 1px solid #d9b8a8; + border: 1px solid var(--platform-surface-border); border-radius: 12px; - background: rgb(255 255 255 / 72%); - color: #765447; + background: var(--platform-button-ghost-fill); + color: var(--platform-button-ghost-text); font-size: 11px; font-weight: 800; + white-space: nowrap; cursor: pointer; } .game-agent-dock-more:hover { - border-color: #c98361; - color: #a7522f; + border-color: var(--platform-surface-hover-border); + color: var(--platform-text-strong); } .game-agent-dock-item { position: relative; display: grid; grid-template-columns: 34px minmax(0, 1fr); + flex: 1 1 128px; align-items: center; gap: 8px; - min-width: 150px; - padding: 7px 10px; - border: 1px solid transparent; + min-width: 0; + max-width: 170px; + padding: 6px 8px; + border: 1px solid var(--platform-icon-border); border-radius: 12px; - background: rgb(255 255 255 / 68%); - color: #584137; + background: var(--platform-button-ghost-fill); + color: var(--platform-text-base); text-align: left; cursor: default; } @@ -5060,8 +5173,8 @@ iframe.preview-frame { width: 34px; height: 34px; border-radius: 12px; - background: #d27a50; - color: #fff; + background: var(--platform-nav-active-fill); + color: var(--platform-warm-text); font-size: 12px; font-weight: 900; place-items: center; @@ -5069,16 +5182,24 @@ iframe.preview-frame { .game-agent-dock-item > span:nth-child(2) { display: grid; + min-width: 0; gap: 2px; } .game-agent-dock-item strong { + overflow: hidden; + color: var(--platform-text-strong); font-size: 11px; + text-overflow: ellipsis; + white-space: nowrap; } .game-agent-dock-item small { - color: #96776a; + overflow: hidden; + color: var(--platform-text-soft); font-size: 9px; + text-overflow: ellipsis; + white-space: nowrap; } .game-agent-dock-item.is-running::after { @@ -5088,8 +5209,8 @@ iframe.preview-frame { width: 7px; height: 7px; border-radius: 50%; - background: #5ec46b; - box-shadow: 0 0 0 3px rgb(94 196 107 / 16%); + background: var(--platform-success-text); + box-shadow: 0 0 0 3px var(--platform-success-border); content: ''; } @@ -5106,18 +5227,18 @@ iframe.preview-frame { } .game-agent-dock-item.is-waiting::after { - background: #df8a16; - box-shadow: 0 0 0 3px rgb(223 138 22 / 16%); + background: var(--platform-warm-text); + box-shadow: 0 0 0 3px var(--platform-warm-border); } .game-agent-dock-item.is-failed::after { - background: #d64545; - box-shadow: 0 0 0 3px rgb(214 69 69 / 16%); + background: var(--platform-button-danger-text); + box-shadow: 0 0 0 3px var(--platform-button-danger-border); } .game-agent-dock-item.is-completed::after { - background: #2878d4; - box-shadow: 0 0 0 3px rgb(40 120 212 / 16%); + background: var(--platform-accent); + box-shadow: 0 0 0 3px var(--platform-nav-active-border); } .game-agent-dock-detail { @@ -5128,11 +5249,11 @@ iframe.preview-frame { display: none; width: 220px; padding: 10px 12px; - border: 1px solid #dcc2b5; + border: 1px solid var(--platform-modal-border); border-radius: 10px; - background: #4f433d; - color: #fff; - box-shadow: 0 12px 30px rgb(50 30 22 / 20%); + background: var(--platform-modal-fill); + color: var(--platform-text-strong); + box-shadow: var(--platform-panel-shadow); transform: translateX(-50%); } @@ -5286,7 +5407,7 @@ iframe.preview-frame { cursor: pointer; } -@media (min-width: 761px) and (min-height: 680px) { +@media (min-width: 761px) { .game-project-workbench { grid-template-rows: minmax(0, 1fr) auto; height: 100dvh; @@ -5385,6 +5506,7 @@ iframe.preview-frame { .game-agent-dock-item { width: 100%; + max-width: none; } .game-agent-dock-detail { 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 fce5fe07b..7af25f6f2 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 @@ -2175,28 +2175,35 @@ export function registerProjectWorkbenchFoundationTests() { ); expect(styles).toMatch( - /\.game-project-workbench\s*\{[^}]*width:\s*calc\(100vw - 72px\)[^}]*padding-top:\s*40px/s, + /\.game-project-workbench\s*\{[^}]*width:\s*calc\(100vw - 72px\)[^}]*padding:\s*40px 8px 8px[^}]*overflow:\s*hidden/s, ); expect(styles).toMatch( - /@media \(min-width: 761px\) and \(min-height: 680px\)[\s\S]*?\.game-project-workbench\s*\{[^}]*grid-template-rows:\s*minmax\(0, 1fr\) auto[^}]*height:\s*100dvh/, + /@media \(min-width: 761px\)[\s\S]*?\.game-project-workbench\s*\{[^}]*grid-template-rows:\s*minmax\(0, 1fr\) auto[^}]*height:\s*100dvh/, ); expect(styles).toMatch( - /\.game-workbench-chat \.project-supervisor-conversation\s*\{[^}]*grid-template-rows:\s*minmax\(120px, 1fr\) minmax\(0, auto\) auto auto auto[^}]*height:\s*100%[^}]*overflow:\s*hidden/s, + /\.game-workbench-chat \.project-supervisor-conversation\s*\{[^}]*grid-template-rows:\s*minmax\(96px, 1fr\) minmax\(0, auto\) auto auto auto[^}]*height:\s*100%[^}]*overflow:\s*hidden/s, ); expect(styles).toMatch( - /\.game-workbench-chat \.agent-runtime-status\s*\{[^}]*max-height:\s*clamp\(190px, 30dvh, 300px\)[^}]*overflow-y:\s*auto/s, + /\.game-workbench-chat \.agent-runtime-status\s*\{[^}]*max-height:\s*clamp\(120px, 24dvh, 240px\)[^}]*overflow-y:\s*auto/s, ); expect(styles).toMatch( /\.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/s, + /\.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, + ); + expect(styles).toMatch( + /\.game-workbench-chat \.project-supervisor-composer button\s*\{[^}]*min-height:\s*72px[^}]*white-space:\s*nowrap/s, ); const agentDockRule = styles.match(/\.game-agent-dock\s*\{([^}]*)\}/s); expect(agentDockRule?.[1]).toContain('overflow: visible;'); expect(agentDockRule?.[1]).not.toContain('overflow-x:'); expect(agentDockRule?.[1]).toContain('z-index: 30;'); expect(agentDockRule?.[1]).toContain('isolation: isolate;'); + expect(agentDockRule?.[1]).toContain('min-height: 58px;'); + expect(styles).toMatch( + /\.game-agent-dock-item\s*\{[^}]*flex:\s*1 1 128px[^}]*min-width:\s*0[^}]*max-width:\s*170px/s, + ); expect(styles).toMatch( /@media \(max-width: 760px\)[\s\S]*?\.game-workbench-layout\s*\{[^}]*height:\s*auto/, ); diff --git a/apps/ai-game-creator-shell/tests/assetCanvasSurface.test.tsx b/apps/ai-game-creator-shell/tests/assetCanvasSurface.test.tsx index ed808d6e5..7cdec8f35 100644 --- a/apps/ai-game-creator-shell/tests/assetCanvasSurface.test.tsx +++ b/apps/ai-game-creator-shell/tests/assetCanvasSurface.test.tsx @@ -416,6 +416,82 @@ afterEach(() => { }); describe('Tauri 素材创作无限画布独立 Surface', () => { + it('直接消费共享 chrome,并暴露工具栏结构与原生按钮状态', async () => { + const memory = memoryHost(); + renderSurface(memory.host); + await screen.findByText('画布可编辑'); + + const toolbar = screen.getByRole('toolbar', { name: '素材画布工具栏' }); + expect( + toolbar.classList.contains('genarrative-image-canvas__toolbar'), + ).toBe(true); + expect( + toolbar.querySelectorAll('.genarrative-image-canvas__toolbar-group'), + ).toHaveLength(3); + expect( + toolbar.querySelectorAll('.genarrative-image-canvas__toolbar-divider'), + ).toHaveLength(2); + + const generate = screen.getByRole('button', { name: 'AI 生成图片' }); + expect( + generate.classList.contains( + 'genarrative-image-canvas__chrome-button', + ), + ).toBe(true); + expect(generate.getAttribute('aria-expanded')).toBe('false'); + expect( + (screen.getByRole('button', { name: '撤销' }) as HTMLButtonElement) + .disabled, + ).toBe(true); + expect( + (screen.getByRole('button', { name: '重做' }) as HTMLButtonElement) + .disabled, + ).toBe(true); + expect( + (screen.getByRole('button', { name: '删除' }) as HTMLButtonElement) + .disabled, + ).toBe(true); + + fireEvent.click(generate); + expect(generate.getAttribute('aria-expanded')).toBe('true'); + expect(screen.getByRole('dialog', { name: 'AI 图片生成' })).toBeTruthy(); + expect( + screen + .getByRole('button', { name: '继续确认' }) + .classList.contains('genarrative-image-canvas__chrome-button'), + ).toBe(true); + expect(screen.getByRole('button', { name: '取消' })).toBeTruthy(); + }); + + it('让生成、失败、保存与取消区域使用内部滚动而不被固定高度裁剪', () => { + const css = readFileSync( + resolve( + process.cwd(), + 'apps/ai-game-creator-shell/src/features/asset-canvas/assetCanvasSurface.css', + ), + 'utf8', + ); + + expect(css).toMatch( + /\.asset-canvas-surface__generation-dialog\s*\{[^}]*max-height:\s*calc\(100% - 32px\)[^}]*overflow:\s*auto/s, + ); + expect(css).toMatch( + /\.asset-canvas-surface__operation-overlay\s*\{[^}]*overflow:\s*auto/s, + ); + expect(css).toMatch( + /\.asset-canvas-surface__operation-card\s*\{[^}]*max-height:\s*100%[^}]*overflow:\s*auto/s, + ); + expect(css).toMatch( + /\.asset-canvas-surface__status\s*\{[^}]*max-height:\s*96px[^}]*overflow-y:\s*auto/s, + ); + expect(css).toMatch( + /\.asset-canvas-surface__savebar\s*\{[^}]*grid-template-columns:/s, + ); + expect(css).toMatch( + /@media \(max-width: 760px\)[\s\S]*?\.asset-canvas-surface__save\s*\{[^}]*grid-column:\s*1 \/ -1[^}]*width:\s*100%/s, + ); + }); + it('取消时先把草稿标记为 cancelled,再返回资源总览', async () => { const memory = memoryHost(); const { onCancel } = renderSurface(memory.host); diff --git a/docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md b/docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md index 7bb15b279..23b5aff16 100644 --- a/docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md +++ b/docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md @@ -85,6 +85,8 @@ ### 3.7 主站 UI 对齐与共享视觉边界 +实现状态(2026-08-06):主站与 Tauri 已完成同源 chrome 接入。Tauri 现有中央素材画布直接消费共享动作按钮、工具栏、工具组和分隔符;工作台外围继续保留四区结构,并以平台 token 统一中央壳、Supervisor、Agent Dock、状态提示和主要操作。生成、保存、登录、计费、草稿、manifest、Runtime 和审批语义未随换肤改变。 + - 项目工作台继续保留左侧平台导航、中央主视窗、右侧 Project Supervisor 和底部专业 Agent 状态栏四区结构;主站图片编辑器只作为视觉语言和共享画布组件的事实源,不把其素材库侧栏、账号业务或云端项目外壳整体搬入客户端。 - 平台主题事实源固定为 `packages/shared/src/theme.css`。画布通用 chrome 固定落在 `@genarrative/image-canvas-react`,主站与 Tauri 必须直接 import 同一组件和作用域样式;客户端不得复制 `src/components/image-editor/`,也不得导入主站完整 `src/index.css`。 - 第一批共享 chrome 固定覆盖画布动作按钮、工具栏、工具分组和分隔符。按钮的默认、悬停、键盘焦点、选中、禁用和主次色语义由共享层表达;宿主只提供图标、文案、事件与业务禁用条件。 @@ -502,10 +504,10 @@ type ProjectAgentMudPointAttribution = { 1. `packages/image-canvas-react` 暴露共享画布动作按钮、工具栏、工具分组和分隔符;主站图片编辑器直接消费这些组件,旧网站组件只允许保留薄适配,不得继续维护另一份按钮可访问性或选中态实现。 2. 共享样式全部使用 `.genarrative-image-canvas*` 作用域并消费 `--platform-*` / `--image-canvas-brand-*` token;Tauri 不导入主站完整 `src/index.css`,主站也不复制共享样式回业务 CSS。 -3. 后续客户端接入完成后,`1280×800` 下中央画布工具栏保持单行或受控横向收纳,中文动作不得逐字换行;聊天输入、主要保存动作和底部 Agent Dock 始终可见,document/body 不产生页面级溢出。 +3. 客户端接入完成后,`1280×800` 下中央画布工具栏保持单行或受控横向收纳,中文动作不得逐字换行;聊天输入、主要保存动作和底部 Agent Dock 始终可见,document/body 不产生页面级溢出。 4. 主站现有图片编辑器的按钮名称、tool selection、hover/focus/pressed/disabled 行为与工具栏位置不因共享抽取回归;网站账号、钱包、素材库、生成和导出逻辑不下沉共享层。 5. 客户端生成、保存、取消、恢复、登录失效与余额不足只更换视觉承载,不改变 Host Port 调用、幂等身份、草稿 CAS、manifest/revision、Runtime 审批或钱包刷新语义。 -6. 阶段一、二只冻结合同并让主站消费共享 chrome,不修改客户端中央素材画布;客户端中央区、Supervisor 和 Agent Dock 的正式换肤在后续同一 UI 实施阶段完成。 +6. 阶段一、二冻结合同并让主站消费共享 chrome;2026-08-06 的阶段三至五已经完成客户端中央区、Supervisor 和 Agent Dock 的正式换肤、定向测试与 `1280×800` 验收。独立 Vite 没有登录会话时继续显示真实登录门禁,不为视觉测试增加绕过入口。 ## 8. 非目标 diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 727c85847..42cd950ff 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -3,10 +3,10 @@ ## 2026-08-06 Game Agent 画布视觉以主站 token 与共享 chrome 为唯一来源 - 背景:网站与 Tauri 已经共同消费 `image-canvas-core/react` 的 viewport、selection、renderer 和 history,但客户端素材画布仍维护独立的文字工具栏、按钮和状态外观,`1280×800` 下会出现动作逐字换行、主次不清和工作台四区视觉漂移。直接复制主站 `src/components/image-editor` 或整包 `src/index.css` 会重新形成宿主分叉和隐式全局依赖。 -- 决策:保留 Game Agent 左导航、中央主视窗、右侧 Supervisor 和底部 Agent Dock 四区布局;平台颜色、边框、文本和 surface 继续以 `packages/shared/src/theme.css` 为事实源,画布动作按钮、工具栏、分组与分隔符进入现有 `@genarrative/image-canvas-react`。主站先改为直接消费共享 chrome,Tauri 在下一 UI 阶段接入同一组件。 +- 决策:保留 Game Agent 左导航、中央主视窗、右侧 Supervisor 和底部 Agent Dock 四区布局;平台颜色、边框、文本和 surface 继续以 `packages/shared/src/theme.css` 为事实源,画布动作按钮、工具栏、分组与分隔符进入现有 `@genarrative/image-canvas-react`。主站和 Tauri 均直接消费同一 chrome,Tauri 宿主只保留业务接线和布局适配。 - 依赖边界:共享组件只接受 React props、图标节点、短文案和事件,不读取账号、钱包、项目、生成、Tauri 或 HTTP。主站 `EditorIconButton` 只保留 Lucide/特殊浮层薄适配,主站业务 CSS 只保留定位和专属状态;Tauri 禁止导入网站完整 CSS。内部错误码、开发者 API 配置和本机绝对路径不作为普通用户默认视觉内容。 -- 分期:阶段一、二只更新权威合同、抽取共享 chrome 并让主站消费,不修改客户端 `AssetCanvasSurface`;阶段三、四再统一中央素材画布、Supervisor 与 Agent Dock,阶段五验证与修复必须和客户端实施同一任务完成。 -- 验证:共享 React 组件与主站图片编辑器 7 个定向测试文件共 `92/92` 通过,覆盖可访问性、状态、透传、工具列表、选中态与 option hover/focus;主站与客户端 typecheck、编码检查和 `git diff --check` 通过。 +- 分期:阶段一、二更新权威合同、抽取共享 chrome 并让主站消费;阶段三、四已统一中央素材画布、Supervisor 与 Agent Dock;阶段五在同一变更中补齐测试、真实视口测量和发现回归修复。 +- 验证:共享 chrome 与 Tauri Surface 定向测试 `20/20` 通过,覆盖可访问性、pressed/expanded/disabled、工具组/分隔符、生成/失败/保存/取消和非裁剪 CSS;AppSurface 横屏合同 `1/1` 通过,锁定 Supervisor composer、Agent Dock 与 `100dvh` 两行工作台。客户端/全仓 typecheck、定向 ESLint、编码和 diff 检查通过。完整 AppSurface 本次运行是 `352/354` 通过:preview 权限用例单跑通过,属于全量异步顺序波动;仍失败的“画板 API Key”运行配置用例不涉及本次 diff,留在原业务范围处理。应用内浏览器 `1280×800` 实测 window/document/body client 与 scroll 均为 `1280×800`;真实入口没有登录会话时保持登录门禁,不增加测试绕过。 - 关联:`docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`、`docs/technical/【技术方案】客户端素材创作无限画布阶段一合同-2026-08-05.md`、`packages/image-canvas-react/`。 ## 2026-08-05 无限画布阶段三以 Tauri 持久草稿和项目 mutation 事务形成独立图片闭环 diff --git a/docs/project-memory/shared-memory/pitfalls.md b/docs/project-memory/shared-memory/pitfalls.md index 3e3322f0a..3b72e0616 100644 --- a/docs/project-memory/shared-memory/pitfalls.md +++ b/docs/project-memory/shared-memory/pitfalls.md @@ -30,6 +30,14 @@ - 验证:共享 React 测试重复 mount/unmount 后 wheel add/remove 数量相等、ResizeObserver 精确 disconnect,并挂两个含各自小地图的 viewport,确认只更新目标实例;网站与 Tauri 壳分别 typecheck/build。 - 关联:`packages/image-canvas-react/src/useImageCanvasViewportControls.ts`、`packages/image-canvas-react/src/CanvasPortal.tsx`、根目录与 `apps/ai-game-creator-shell` 的 Vite 配置。 +## 桌面工作台不要让 Supervisor 内容高度挤掉输入区和 Agent Dock + +- 现象:`1280×800` 或更矮窗口中,Supervisor 的消息、Runtime 状态和错误正文共同按内容高度增长,聊天输入被推到栏外;底部 Dock 使用固定宽度卡片时还会在中等宽度造成页面级横向溢出。中央画布的生成卡和状态栏若同时固定高度并隐藏 overflow,进度、失败、重试、保存或取消动作会被裁掉。 +- 原因:四区工作台没有把“主区内部滚动”和“页面级滚动”分开;Supervisor 的长状态没有独立上限,Dock 卡片不能收缩,宿主又用全局按钮/固定行高样式覆盖共享 chrome。 +- 处理:桌面工作台使用 `100dvh` 两行网格,第一行 `minmax(0, 1fr)` 承载中央区与 Supervisor,第二行承载 Dock;消息和 Runtime 分别内部滚动,composer 保持最后一行并设置明确层级。Dock 卡片使用可收缩 flex 与文本省略。画布 dialog、进度/失败卡和状态栏设置 `min-height: 0`、受限最大高度与内部滚动;宿主不再覆盖所有按钮,只为主要动作和布局提供 token 化薄样式。 +- 验证:AppSurface CSS 合同检查 `100dvh`、Supervisor composer、Runtime 内滚动、Dock 常驻/可收缩;素材画布测试检查生成 dialog、operation card、状态栏和窄屏保存动作不会被裁剪。真实入口在 `1280×800` 测量 document/body client 与 scroll 一致;登录门禁不可为视觉测试绕过。 +- 关联:`apps/ai-game-creator-shell/src/styles.css`、`apps/ai-game-creator-shell/src/features/asset-canvas/assetCanvasSurface.css`、`apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts`、`apps/ai-game-creator-shell/tests/assetCanvasSurface.test.tsx`。 + ## 正式素材提交不能把多文件写入或 Tauri 事件误当成一次原子动作 - 现象:图片已经落到 `assets/` 但 manifest 没有资产,或 manifest 已追加而 project revision/草稿仍是旧值;进程在 emit 前后退出后,用户重试又得到第二份图片、第二个 asset 或重复选中。 diff --git a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md index 6615725e8..8c4215bc5 100644 --- a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md +++ b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md @@ -960,3 +960,11 @@ game-project/ - 应用重启时 `accepted/running` 只恢复 GET,`prepared` 只以私有账本原请求字节和原键恢复同一 POST。用户停止等待只失效当前 focus generation;不可取消的远端操作继续 reconcile,迟到 completed 可以更新原项目但不能抢焦点。配置缺失、结果引用不稳定、下载/提交失败和未知结果都不得显示资源创建成功。 - 最终审计后 Tauri 撤销/重做直接使用与网站相同的共享 `useCanvasHistory`,共享 history 同时恢复 viewport、选择、图层位置和缩放边界;Tauri 自身只保留 Pointer 事件接线,平移、缩放、选择、图层变换和 renderer 算法继续来自共享 core/react。 - 正式事务把首个快照、全部快照和 journal 写入后但 ledger 写入前纳入故障矩阵。恢复发现无 ledger 的 transaction 时,只有目录内容全部属于受控快照/临时文件、正式目标文件不存在、manifest 与 project revision 仍精确等于 before,才清理目录并返回 rolled-back;任何未知文件或权威状态变化都失败关闭。同一 commit/idempotency 身份在清理后可重放,最终仍只登记一份资产。 + +## 2026-08-06 Game Agent 画布 UI 对齐阶段三至五 + +- 中央 `AssetCanvasSurface` 沿用现有 create/refine、生成、保存、取消和恢复接线,直接消费 `@genarrative/image-canvas-react` 的 `CanvasChromeButton / CanvasToolbar / CanvasToolbarGroup / CanvasToolbarDivider`。宿主不再维护全局按钮皮肤,只保留字段、面板定位、主要动作和平台 token 映射;主站完整 `src/index.css` 没有进入 Tauri。 +- 顶部动作与保存设置分层,工具组保持中文单行并允许工具栏受控横向收纳;生成设置仍是独立 dialog。进度/失败卡、dialog 和底部状态栏使用内部滚动,不再由固定高度或父级 overflow 裁剪,保存/确认使用平台 primary 层级。 +- 工作台仍是左导航、中央主视窗、右 Project Supervisor、底部 Agent Dock 四区。中央区使用主面板层级;Supervisor 的消息/Runtime 独立滚动且 composer 常驻;Dock 改为较轻的持续状态 surface 与可收缩卡片。桌面网格按 `100dvh` 将 Dock 固定在第二行,`≤760px` 只保留开发兼容堆叠。 +- AppSurface/CSS 合同锁定 `1280×800` 下的工作台无页面级溢出、Supervisor composer、主要保存动作和 Dock 可见;共享/Tauri 定向用例锁定按钮名称、工具组/分隔符、pressed/expanded/disabled 和生成/失败/保存/取消非裁剪。真实 Vite 页面测得 window/document/body client 与 scroll 均为 `1280×800`;无登录会话时按既有安全合同停在登录门禁,不增加或使用测试绕过。 +- 本次只调整 UI 和测试/文档,未改变图片生成请求、登录态、泥点计费、钱包刷新、Host Port、Tauri command/event、草稿 CAS、manifest/revision、幂等恢复、Runtime、审批、资源投影或保存事务。 diff --git a/docs/technical/【技术方案】客户端素材创作无限画布阶段一合同-2026-08-05.md b/docs/technical/【技术方案】客户端素材创作无限画布阶段一合同-2026-08-05.md index 4769de2ef..37cfe351c 100644 --- a/docs/technical/【技术方案】客户端素材创作无限画布阶段一合同-2026-08-05.md +++ b/docs/technical/【技术方案】客户端素材创作无限画布阶段一合同-2026-08-05.md @@ -2,7 +2,7 @@ 更新时间:`2026-08-06` -状态:阶段一产品与技术合同已冻结;截至 2026-08-06,客户登录态图片生成、泥点计费链路、中央进度/失败态和 Tauri 正式资产提交已落地,后续增量仍受本文合同约束。 +状态:阶段一产品与技术合同已冻结;截至 2026-08-06,客户登录态图片生成、泥点计费链路、中央进度/失败态、Tauri 正式资产提交及画布 UI 对齐阶段三至五已落地,后续增量仍受本文合同约束。 本文是网站与 AI 游戏创作 Tauri 客户端共享图片画布能力的下一阶段编码依据。若本文与资源管理阶段七的“美术编辑暂缓”口径冲突,以本文对后续素材创作切片的更新决定为准;资源总览既有布局、依赖图和只读聚焦合同继续有效。 @@ -204,7 +204,7 @@ interface ImageCanvasHostPort { ### 3.4 主站 UI 对齐与共享画布 chrome -实现状态(2026-08-06,阶段一、二):以下四个共享组件及作用域样式已经进入 `@genarrative/image-canvas-react`;主站默认 `EditorIconButton`、底部画布工具栏及画布内直接使用的常规图标/短标签动作已经消费共享按钮,网站专属 `surfaceFloating` / `darkMini` 仍保留薄适配。客户端 `AssetCanvasSurface` 尚未接入,按本节边界留给后续 UI 实施阶段。 +实现状态(2026-08-06,阶段一至五):以下四个共享组件及作用域样式已经进入 `@genarrative/image-canvas-react`;主站默认 `EditorIconButton`、底部画布工具栏及画布内直接使用的常规图标/短标签动作已经消费共享按钮,网站专属 `surfaceFloating` / `darkMini` 仍保留薄适配。客户端 `AssetCanvasSurface` 的常规动作、三组工具、两个分隔符、保存/生成/失败动作和浮动缩放栏也已直接消费同一 chrome;宿主只保留业务回调、字段、面板布局和平台 token 映射。 第一批通用视觉组件固定由 `@genarrative/image-canvas-react` 暴露: @@ -220,7 +220,7 @@ CanvasToolbarDivider - 共享 CSS 继续位于 `packages/image-canvas-react/src/styles.css`,选择器必须以 `.genarrative-image-canvas` 前缀开头。颜色优先消费宿主继承的 `--image-canvas-brand-*`,再回退 `--platform-*`,最后才使用包内安全默认值。 - 主站现役 `EditorIconButton` 可以保留为传入 Lucide icon 的薄 adapter,但默认画布按钮必须委托 `CanvasChromeButton`;特殊网站浮层或暗色媒体按钮仍可留在宿主,不能为了共享视觉把网站 portal、账号或媒体业务下沉。 - 主站底部画布工具栏必须委托 `CanvasToolbar`、`CanvasToolbarGroup` 和 `CanvasToolbarDivider`,位置、引导态和工具 option hover 仍由网站宿主控制。共享抽取完成后,主站业务 CSS 只保留定位和主站专属状态,不重复维护通用按钮、浮动 surface、分组和分隔符样式。 -- Tauri 在后续 UI 实施阶段消费同一组件;本阶段不修改 `AssetCanvasSurface` 的 JSX/CSS,不把生成、保存、草稿、manifest 或 Runtime 逻辑带入共享包。 +- Tauri 直接消费同一组件,且没有把生成、保存、草稿、manifest、账号、钱包、HTTP 或 Runtime 逻辑带入共享包;这些事实继续由现有 Host Adapter 和宿主回调负责。 - 主题事实源仍是 `packages/shared/src/theme.css`。Tauri 只能导入共享主题、`@genarrative/image-canvas-react/styles.css` 和自身宿主样式,禁止导入网站整包 `src/index.css`。 - 共享组件测试必须覆盖名称、默认 `type=button`、pressed/expanded、禁用、带短标签动作、floating toolbar surface 和 ref/class/event 透传;主站测试继续锁定工具列表、选中态和 option tool 的 hover/focus 行为。 @@ -957,3 +957,12 @@ confirmation-required - `accepted/running` 重启只查询原 operation;`prepared` 只以账本中的原 endpoint、原始字节和原键恢复同一 POST。轮询超时、临时鉴权/网关错误或结果未知保持可恢复,不自动转 failed 或换键扣费。 - 用户“停止等待”只撤销当前 session 的聚焦意图,不伪装远端取消。后台 command 可以继续安全 reconcile;应用退出后由项目恢复扫描继续原 operation。迟到完成可以更新原项目权威缓存和 manifest,但不能切回旧画布、清搜索或抢焦点。 - 登录失效、余额不足、平台生成服务配置错误、远端 terminal failed、稳定引用缺失、下载失败、staging/正式提交失败都不得返回资源创建成功。客户模式不得把缺少开发者 API Key 显示为失败原因。只有 terminal failed 才允许用户通过新的独立确认建立新 intent;其它未知或对账状态必须保留原身份。 + +## 15. 2026-08-06 画布 UI 对齐阶段三至五收口 + +- 阶段三没有新建平行画布。现有 `AssetCanvasSurface` 直接使用 `CanvasChromeButton`、`CanvasToolbar`、`CanvasToolbarGroup` 和 `CanvasToolbarDivider`,覆盖返回、导入、撤销、重做、删除、AI 生成、保存、生成确认、失败重试、停止等待和缩放。共享组件只接收图标节点、名称、事件和原生状态,不读取 Tauri、账号、钱包或 HTTP。 +- Tauri 样式只导入 `packages/shared/src/theme.css`、`@genarrative/image-canvas-react/styles.css` 与客户端宿主样式,没有导入主站 `src/index.css`。宿主删除了覆盖全部按钮的重复视觉规则,只保留保存/确认主操作、字段、弹窗、进度/失败卡、状态栏和 viewport 定位;颜色、边框、surface、文字、focus 和主次动作均使用平台 token。 +- 生成设置继续通过独立 dialog 展示。生成/失败卡和底部状态使用受限高度加内部滚动,保存设置在窄宽下换为两行并保持主按钮整行可见;工具组不拆字,工具栏需要时横向收纳。普通确认文案不显示 manifest、幂等实现、开发者 API 配置或本机绝对路径。 +- 阶段四保留左导航、中央主视窗、右侧 Project Supervisor 和底部 Agent Dock 四区 DOM。中央壳使用主面板层级;Supervisor 降为协作面板,其消息和 Runtime 状态内部滚动、composer 固定在栏底;Dock 使用较轻 surface 和可收缩状态卡,不与中央主要操作争抢层级。 +- 桌面 `>760px` 统一使用 `100dvh` 的两行工作台网格,第一行是内部滚动的主视窗/Supervisor,第二行是常驻 Dock。正式 `1280×800` 基线下页面级尺寸合同为 window/document/body `1280×800`;无登录会话的真实 Vite 入口按安全合同停在登录页,不绕过门禁,工作台内部由 AppSurface、共享 chrome 和 CSS 合同测试提供可重复证据。 +- 阶段五定向测试覆盖共享 chrome 的 pressed、Tauri 实际消费的工具组/分隔符、按钮名称、expanded/disabled、生成确认、失败重试、保存/取消,以及生成 dialog、进度/失败卡、状态栏、Supervisor composer 和 Agent Dock 的非裁剪/可见性合同。生成请求、登录态、泥点计费、Host Port、草稿 CAS、manifest/revision、Runtime、审批和保存事务均未改变。