验收修正:任务锚点改挂画布那一格,展开后开关让位
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Failing after 7m22s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Failing after 7m32s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Failing after 7m46s
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 7m50s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m10s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 3m8s
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Failing after 7m22s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Failing after 7m32s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Failing after 7m46s
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 7m50s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m10s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 3m8s
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
- 重叠:右上角锚点不再用 position: absolute + 写死的 top(工具条是 stage 第一行、高度随按钮换行变化),改为 stage 网格里与工作面同一个单元格的条目:grid-row 按 placement 分档 + grid-column 1 + justify-self end + align-self start + margin;同格的三处容器也显式钉住第 1 列,避免画布被自动列放置挤进隐式第二列。 - 并排:开关只在完全收起时渲染(收起动画期间也不画),展开态画布右上角只留面板,收起走面板头部 × 或点画布外部。 - 测试:面板用例新增「展开时开关让位」、计数用例拆成收起查开关 / 展开查面板;样式用例改判网格条目坐标与窄屏 justify-self: stretch;dismiss 用例改走面板 × 收起、收起后开关回来能再打开。 - 文档:PRD 3.10 与更新时间、decision-log 新增 2026-09-21 验收修正条目、待办文档补登记该修正。
This commit is contained in:
+8
-4
@@ -230,14 +230,15 @@ function resourceCanvasGenerationTaskRowNode(
|
||||
* 画布上的「生成任务」侧栏(常驻、可折叠、非模态)。
|
||||
*
|
||||
* 形态对齐网页端美术画布的任务侧栏:**画布右上角**的一枚独立开关 + 展开时贴着它的浮动面板
|
||||
* (开关始终在,收起态只剩开关),面板是一列任务(画布照常可交互)。它**不是**模态浮层:
|
||||
* ——收起态只有那一枚开关,展开态只有面板(开关让位,不并排;面板自己带 × 收起),
|
||||
* 面板是一列任务(画布照常可交互)。它**不是**模态浮层:
|
||||
* 不铺全屏遮罩、不做焦点陷阱、不进 `isResourceCanvasFloatingPanelOpen` /
|
||||
* `resourceCanvasHostGenerationPanelOpen` 的遮挡判据;折叠也不影响任务推进——任务活在账本与
|
||||
* 本地队列里,与这个视图的生命周期无关。
|
||||
*
|
||||
* 位置取舍:右上角是画布上唯一「既不被对话面板占、也不和底部工具栏 / 右下角缩放 Dock 打架」的
|
||||
* 稳定空位,与美术画布同一处;锚点是**覆盖式**而不是把画布挤窄(画布的视口数学与资源卡排布都
|
||||
* 不被 reflow 动到,收起即完全让出画布,只留那一枚开关)。宽度 300px 落在 280–340px 区间内。
|
||||
* 不被 reflow 动到,收起即只留那一枚开关)。宽度 300px 落在 280–340px 区间内。
|
||||
*
|
||||
* 分栏、条数、封顶、折叠语义都在这里;颜色与排版在同目录的 CSS 文件里,取值全部走
|
||||
* `--platform-*` 设计 token。
|
||||
@@ -344,10 +345,13 @@ export function ResourceCanvasAssetGenerationTasksPanelView({
|
||||
}, [open]);
|
||||
|
||||
/**
|
||||
* 右上角开关:收起态只剩它(画布完全让出),展开态它是「已经开着的那一枚」。
|
||||
* 右上角开关只在**完全收起**时渲染:展开态把开关藏起来(验收口径「开关与面板不该并排,
|
||||
* 详情出来以后入口就该隐藏」——收起改走面板自己的 × 与点外部),收起动画期间也不画,
|
||||
* 否则那 160ms 里两者又会同框。
|
||||
*
|
||||
* 在途计数只在这里算一次,开关与面板头部共用同一个值,两处不会各算一套。
|
||||
*/
|
||||
const showToggle = !open && phase === 'idle';
|
||||
const toggle = (
|
||||
<button
|
||||
type="button"
|
||||
@@ -368,7 +372,7 @@ export function ResourceCanvasAssetGenerationTasksPanelView({
|
||||
className="platform-theme platform-theme--light game-resource-generation-tasks-anchor"
|
||||
data-generation-tasks-placement={placement}
|
||||
>
|
||||
{toggle}
|
||||
{showToggle ? toggle : null}
|
||||
{/* 收起要先把退场动画播完(`is-leaving`)再卸载,所以 `phase` 未回到 idle 时面板还在。 */}
|
||||
{open || phase !== 'idle' ? (
|
||||
<aside
|
||||
|
||||
+28
-18
@@ -1,11 +1,12 @@
|
||||
/* 「生成任务」侧栏的样式。
|
||||
*
|
||||
* 分层与网页端美术画布的任务侧栏(`ImageCanvasTaskSidebarView.tsx` + `src/index.css:6084+`)一一对应:
|
||||
* 右上角锚点(开关 + 浮动面板)→ 头部(标题 + 在途计数)→ 分栏标题(各带条数)→
|
||||
* 右上角锚点(收起态是开关、展开态是浮动面板,同一时刻只有其一)→ 头部(标题 + 在途计数)→
|
||||
* 分栏标题(各带条数)→
|
||||
* 条目卡片(状态徽标 / 阶段 / 耗时 / 定位)。
|
||||
*
|
||||
* 位置与开合形态照抄美术画布:**开关常驻画布右上角,面板在开关左侧展开**,收起态只剩那一枚
|
||||
* 开关(美术画布同样如此:列表为空且侧栏关闭时只保留图标开关)。差别只有一处:
|
||||
* 位置与开合形态照抄美术画布:**画布右上角那枚开关是收起态的入口,展开后开关让位、只留面板**
|
||||
* (两者不同框 —— 初版把它们并排摆过,验收现场直接判为不对)。差别只有一处:
|
||||
* **颜色与外形全部走 AGC 的平台设计 token(`--platform-*`)**,不再照抄网页端的固定色值,
|
||||
* 这样亮/暗主题都跟着 `platform-theme` 走,也不引入第二套色板。
|
||||
*
|
||||
@@ -13,30 +14,38 @@
|
||||
*/
|
||||
|
||||
/*
|
||||
* 右上角锚点:开关与面板同挂在这里,所以「收起只剩开关、展开面板贴在同一角」这两个状态
|
||||
* 共用同一处坐标,不会出现开关与面板各锚一处、开合时互相漂移。
|
||||
* 右上角锚点:开关与面板同挂在这里,所以「收起只剩开关、展开只剩面板」这两个状态共用同一处
|
||||
* 坐标,不会出现开关与面板各锚一处、开合时互相漂移。
|
||||
*
|
||||
* 锚点相对 `.game-workbench-stage` 定位(stage `position: relative`,见 styles.css):
|
||||
* 工具条占第一行(min-height 48px),下面的 auto 行在资源态是空行(高 0),所以
|
||||
* `top: 3.5rem` 正好落在画布区顶边再留 8px —— 资源画布右上角。运行表现层是另一个工作面:
|
||||
* 它的右上角(`top: 20px; right: 20px`)被版本入口占着,锚点因此下移到版本入口之下。
|
||||
* 锚点是 stage 网格里**与工作面同一个单元格**的另一个条目(`grid-row` 按 `placement` 分档 +
|
||||
* `justify-self: end` 贴右 + `align-self: start` 贴顶),**不用 `position: absolute`**:
|
||||
* 绝对定位要写死一个 `top`,而工具条(stage 第一行)的高度会随按钮换行变化 —— 那个写死的
|
||||
* 偏移量迟早会压在工具条上(验收现场那条「生成任务和菜单栏重叠」就是这么来的)。
|
||||
* 放进同一个网格单元格以后,「画布顶边 + 一小段内缩」由网格自己保证,工具条多高都不用管。
|
||||
*
|
||||
* 分档:资源栏目画布与 UI 编辑器在第二 / 第三行,运行表现层占 `2 / -1` 且它右上角
|
||||
* (`top: 20px; right: 20px`)被版本入口占着,所以那一档把内缩上边距加大到版本入口之下。
|
||||
*/
|
||||
.game-resource-generation-tasks-anchor {
|
||||
position: absolute;
|
||||
top: 3.5rem;
|
||||
right: 0.85rem;
|
||||
grid-row: 3;
|
||||
grid-column: 1;
|
||||
justify-self: end;
|
||||
align-self: start;
|
||||
margin: 0.85rem;
|
||||
z-index: 40;
|
||||
display: flex;
|
||||
/* 锚点被拉到整宽时(窄屏),开关与面板仍然贴住右上角,而不是散在左侧。 */
|
||||
justify-content: flex-end;
|
||||
align-items: flex-start;
|
||||
gap: 0.45rem;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.game-resource-generation-tasks-anchor[data-generation-tasks-placement='run'] {
|
||||
top: 7rem;
|
||||
right: 1.25rem;
|
||||
grid-row: 2 / -1;
|
||||
margin: 3.5rem 1.1rem 0.85rem 0.85rem;
|
||||
}
|
||||
|
||||
.game-resource-generation-tasks-anchor[data-generation-tasks-placement='editor'] {
|
||||
grid-row: 2;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -417,8 +426,9 @@
|
||||
/* 窄屏(含 360px):侧栏占满可用宽度,不挡画布操作。 */
|
||||
@media (max-width: 480px) {
|
||||
.game-resource-generation-tasks-anchor {
|
||||
left: 0.5rem;
|
||||
right: 0.5rem;
|
||||
/* 拉满画布那一格:`justify-self: end` 只让锚点贴右,窄屏要的是整宽。 */
|
||||
justify-self: stretch;
|
||||
margin: 0.5rem;
|
||||
}
|
||||
|
||||
.game-resource-generation-tasks-sidebar {
|
||||
|
||||
@@ -5900,6 +5900,9 @@ iframe.preview-frame {
|
||||
|
||||
.game-workbench-stage > .game-resource-manager {
|
||||
grid-row: 3;
|
||||
/* 显式钉住第 1 列:右上角任务锚点是同一格里第二个网格条目,画布若走自动列放置会被挤到
|
||||
第二列(隐式列),画布会被压窄一半。 */
|
||||
grid-column: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
@@ -5945,8 +5948,9 @@ iframe.preview-frame {
|
||||
}
|
||||
|
||||
.game-workbench-stage[data-resource-view-state='resources.ui-editor']
|
||||
> div:last-child {
|
||||
> .game-workbench-editor-shell {
|
||||
grid-row: 2;
|
||||
grid-column: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
@@ -8395,6 +8399,9 @@ iframe.preview-frame {
|
||||
display: grid;
|
||||
grid-template-rows: minmax(300px, 1fr) auto;
|
||||
grid-row: 2 / -1;
|
||||
/* 与右上角任务锚点同格(见 resourceCanvasAssetGenerationTasksSidebar.css):显式钉第 1 列,
|
||||
避免画布被自动列放置挤到隐式列里。 */
|
||||
grid-column: 1;
|
||||
gap: 12px;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
|
||||
@@ -237,16 +237,23 @@ describe('「生成任务」侧栏', () => {
|
||||
onFocusTask={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
expect(
|
||||
screen.getByRole('button', { name: '生成任务' }).dataset
|
||||
.resourceGenerationTaskCount,
|
||||
).toBe('0');
|
||||
expect(screen.getByRole('button', { name: '生成任务' }).textContent).toBe(
|
||||
'生成任务',
|
||||
);
|
||||
expect(screen.getByLabelText('在途生成任务 0').textContent).toBe('0');
|
||||
});
|
||||
|
||||
test('展开时开关让位:同一时刻只有面板自己那枚 × 能收起它', async () => {
|
||||
const user = userEvent.setup();
|
||||
const { onToggleOpen } = renderSidebar([task({ taskId: 't1' })], {
|
||||
open: true,
|
||||
});
|
||||
// 开关与面板不并排:展开态画布右上角只留面板(收起走面板头部那枚 × 或点外部)。
|
||||
expect(
|
||||
screen.queryByRole('button', { name: /^生成任务(?: · \d+)?$/ }),
|
||||
).toBeNull();
|
||||
expect(screen.getByRole('region', { name: '生成任务' })).not.toBeNull();
|
||||
await user.click(screen.getByRole('button', { name: '关闭生成任务' }));
|
||||
expect(onToggleOpen).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('锚点按工作面分档:资源画布 / 运行表现层 / UI 编辑器各挂一档', () => {
|
||||
const { container, rerender } = renderSidebar([], { placement: 'run' });
|
||||
const placementOf = () =>
|
||||
|
||||
+23
-15
@@ -201,24 +201,33 @@ describe('「生成任务」侧栏样式', () => {
|
||||
|
||||
test('锚点贴在工作面右上角,运行表现层让开版本入口,开关沿用次级胶囊样式', () => {
|
||||
const anchor = resolved(['.game-resource-generation-tasks-anchor']);
|
||||
expect(declaration(anchor, 'position')).toBe('absolute');
|
||||
expect(declaration(anchor, 'top')).toBe('3.5rem');
|
||||
expect(declaration(anchor, 'right')).toBe('0.85rem');
|
||||
// 锚点是 stage 网格里与工作面同一个单元格的条目:贴右贴顶由网格给,不再用写死 top 的绝对定位
|
||||
//(写死的 top 会被工具条换行顶穿,验收现场那条「生成任务和菜单栏重叠」就是这么来的)。
|
||||
expect(anchor.has('position')).toBe(false);
|
||||
expect(declaration(anchor, 'grid-row')).toBe('3');
|
||||
expect(declaration(anchor, 'grid-column')).toBe('1');
|
||||
expect(declaration(anchor, 'justify-self')).toBe('end');
|
||||
expect(declaration(anchor, 'align-self')).toBe('start');
|
||||
expect(declaration(anchor, 'z-index')).toBe('40');
|
||||
expect(declaration(anchor, 'align-items')).toBe('flex-start');
|
||||
// 画布与开关之间只留一档小间距,开关不会盖住面板。
|
||||
expect(declaration(anchor, 'gap')).toBe('0.45rem');
|
||||
// 画布顶边内缩一小段,不压在工具条那一行上。
|
||||
expect(declaration(anchor, 'margin')).toBe('0.85rem');
|
||||
|
||||
// 运行表现层右上角被版本入口(`game-run-version-picker`:top 20px / right 20px)占着,
|
||||
// 这一档必须把开关压到那枚版本入口下面。
|
||||
// 这一档必须把开关压到那枚版本入口下面(内缩上边距 3.5rem)。
|
||||
const runAnchor = resolved([
|
||||
'.game-resource-generation-tasks-anchor',
|
||||
".game-resource-generation-tasks-anchor[data-generation-tasks-placement='run']",
|
||||
]);
|
||||
expect(declaration(runAnchor, 'top')).toBe('7rem');
|
||||
expect(
|
||||
Number.parseFloat(declaration(runAnchor, 'top') as string),
|
||||
).toBeGreaterThan(5);
|
||||
expect(declaration(runAnchor, 'grid-row')).toBe('2 / -1');
|
||||
expect(declaration(runAnchor, 'margin')).toContain('3.5rem');
|
||||
|
||||
// UI 编辑器那一档与资源画布同档(第二行),不引入第三套坐标。
|
||||
const editorAnchor = resolved([
|
||||
'.game-resource-generation-tasks-anchor',
|
||||
".game-resource-generation-tasks-anchor[data-generation-tasks-placement='editor']",
|
||||
]);
|
||||
expect(declaration(editorAnchor, 'grid-row')).toBe('2');
|
||||
expect(declaration(editorAnchor, 'margin')).toBe('0.85rem');
|
||||
|
||||
// 开关保留原来工具条那一枚的外观(次级胶囊),只是搬到画布上并加一档面板投影;
|
||||
// 取值仍然全部走平台 token,不引入第二套色板。
|
||||
@@ -243,14 +252,13 @@ describe('「生成任务」侧栏样式', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('窄屏(360px)下锚点占满可用宽度,面板宽度自适应不溢出', () => {
|
||||
test('窄屏(360px)下锚点拉满画布那一格,面板宽度自适应不溢出', () => {
|
||||
const narrowAnchor = resolved(
|
||||
['.game-resource-generation-tasks-anchor'],
|
||||
360,
|
||||
);
|
||||
expect(declaration(narrowAnchor, 'left')).toBe('0.5rem');
|
||||
expect(declaration(narrowAnchor, 'right')).toBe('0.5rem');
|
||||
expect(declaration(narrowAnchor, 'justify-content')).toBe('flex-end');
|
||||
expect(declaration(narrowAnchor, 'justify-self')).toBe('stretch');
|
||||
expect(declaration(narrowAnchor, 'margin')).toBe('0.5rem');
|
||||
|
||||
const narrowSidebar = resolved(
|
||||
['.game-resource-generation-tasks-sidebar'],
|
||||
|
||||
+16
-9
@@ -12,6 +12,7 @@ import {
|
||||
render,
|
||||
screen,
|
||||
waitFor,
|
||||
within,
|
||||
} from './appSurface/harness';
|
||||
import { repoPath } from './repoPath';
|
||||
|
||||
@@ -104,21 +105,25 @@ describe('「生成任务」侧栏的开合与入口位置', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('点侧栏内部与开合按钮都不收起:内部保持打开,按钮仍能 toggle 收起来', async () => {
|
||||
it('展开时开关让位:点侧栏内部不收起,收起只走面板自己的 ×', async () => {
|
||||
const sidebar = await renderWorkbench('workbench-tasks-dismiss-inside');
|
||||
// 侧栏内部点击(任务列表区域)不该被当成点外部。
|
||||
fireEvent.pointerDown(sidebar);
|
||||
expect(screen.queryByRole('region', { name: '生成任务' })).not.toBeNull();
|
||||
|
||||
// 开合按钮自己负责 toggle:点一次必须真的收起(不能先被「点外部」收起再被 toggle 打开)。
|
||||
const entry = screen.getByRole('button', { name: '生成任务' });
|
||||
fireEvent.pointerDown(entry);
|
||||
fireEvent.click(entry);
|
||||
// 展开态开关不渲染(开关与面板不并排):这一刻画布上只有面板自己那枚 ×。
|
||||
expect(
|
||||
screen.queryByRole('button', { name: /^生成任务(?: · \d+)?$/ }),
|
||||
).toBeNull();
|
||||
fireEvent.click(
|
||||
within(sidebar).getByRole('button', { name: '关闭生成任务' }),
|
||||
);
|
||||
await waitFor(() =>
|
||||
expect(screen.queryByRole('region', { name: '生成任务' })).toBeNull(),
|
||||
);
|
||||
|
||||
// 再点一次要能回来。
|
||||
// 收起之后开关回来,点它能再打开。
|
||||
const entry = await screen.findByRole('button', { name: '生成任务' });
|
||||
fireEvent.click(entry);
|
||||
expect(
|
||||
await screen.findByRole('region', { name: '生成任务' }),
|
||||
@@ -143,13 +148,15 @@ describe('「生成任务」侧栏的开合与入口位置', () => {
|
||||
// 依赖 / 类型仍然收在行尾。
|
||||
expect(sortIndex).toBe(buttons.length - 2);
|
||||
|
||||
const toggle = screen.getByRole('button', { name: '生成任务' });
|
||||
const anchor = toggle.closest('.game-resource-generation-tasks-anchor');
|
||||
// 锚点挂在主视窗(stage)的**画布那一格**里:右上角坐标是相对画布工作面算的,
|
||||
// 不是相对整个窗口,也不会压在工具条那一行上。
|
||||
const anchor = document.querySelector(
|
||||
'.game-resource-generation-tasks-anchor',
|
||||
);
|
||||
expect(anchor).not.toBeNull();
|
||||
expect(anchor?.getAttribute('data-generation-tasks-placement')).toBe(
|
||||
'canvas',
|
||||
);
|
||||
// 锚点挂在主视窗(stage)里:右上角坐标是相对画布工作面算的,不是相对整个窗口。
|
||||
expect(
|
||||
document
|
||||
.querySelector('.game-workbench-stage')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# AI 游戏创作项目开发工作台 PRD
|
||||
|
||||
更新时间:`2026-09-21`(2026-09-20 音频生成并入图片类那份后台任务账本、派生/修改类任务并入同一「生成任务」侧栏,2026-09-21 卡片浮层改为「提交即关」、**「生成任务」侧栏从画布左侧贴边改为画布右上角锚点(开关常驻)**、**资源卡可拖到对话栏批量 @ 引用**、**替换面板改为非模态浮层并支持在画布上点选目标**,见 §3.10 / §5.3 / §7.8 / §7.9 与 [`【功能说明】AGC聊天素材引用`](../【功能说明】AGC聊天素材引用-2026-09-08.md);2026-09-14 图片类生成后台化:入口 IPC 为 `start_local_project_asset_generation` + 项目内任务账本 + 本地排队 + 非模态「生成任务」面板;2026-09-13 新增的功能画布底部工具栏入口矩阵 §3.10 / §7.9,以及右侧 Supervisor 对话气泡、可访问对比度与过程卡布局收口,资源卡预览、分区布局、非破坏性资源编辑、资源替换与 Godot 双根合同保持不变)
|
||||
更新时间:`2026-09-21`(2026-09-20 音频生成并入图片类那份后台任务账本、派生/修改类任务并入同一「生成任务」侧栏,2026-09-21 卡片浮层改为「提交即关」、**「生成任务」侧栏从画布左侧贴边改为画布右上角锚点(开关常驻)**、**资源卡可拖到对话栏批量 @ 引用**、**替换面板改为非模态浮层并支持在画布上点选目标**(验收现场两条修正:任务锚点改挂画布那一格、展开后开关让位),见 §3.10 / §5.3 / §7.8 / §7.9 与 [`【功能说明】AGC聊天素材引用`](../【功能说明】AGC聊天素材引用-2026-09-08.md);2026-09-14 图片类生成后台化:入口 IPC 为 `start_local_project_asset_generation` + 项目内任务账本 + 本地排队 + 非模态「生成任务」面板;2026-09-13 新增的功能画布底部工具栏入口矩阵 §3.10 / §7.9,以及右侧 Supervisor 对话气泡、可访问对比度与过程卡布局收口,资源卡预览、分区布局、非破坏性资源编辑、资源替换与 Godot 双根合同保持不变)
|
||||
|
||||
## 1. 产品定位
|
||||
|
||||
@@ -164,7 +164,7 @@
|
||||
- 位置与层级:画布左下角(`left: 14px; bottom: 14px; z-index: 40`)。右下角是既有的缩放 / 撤销 Dock(`right: 14px; bottom: 14px`),左下角是画布上唯一两者都不占的稳定空位。工具栏是管理区 `.game-resource-book-manager` 的**直接子节点**、与画本场景并列,不进带 `scale()` 的场景层;二级菜单与「入口不可用原因」都贴着工具栏上沿弹出,不做内嵌内容。
|
||||
- 外壳用共享 chrome(`packages/image-canvas-react` 的 `CanvasToolbar / CanvasToolbarGroup / CanvasChromeButton`),样式落在 AGC 的 `resourceCanvasChrome.css`;共享包只承接通用表现,不含业务规则。
|
||||
- 接线:图片类入口走本地 IPC `start_local_project_asset_generation`(`kind` ∈ `image / character / spec / icon-spec / ui-prototype / art-spritesheet`;**提交即返回任务记录**,生成由 Rust 后台任务跑完写回项目,进度用 `list_local_project_asset_generations` 读回项目内账本 `.agent/runtime/asset-generation-tasks/tasks.json`);音频入口也走 `start_local_project_asset_generation`(同一份项目内任务账本;`kind` = `sound-effect` / `background-music`,并额外携带该次生成的请求身份 `idempotencyKey`,任务 id 即该次生成的 operation id;生成仍复用既有音频无源生成链路,`generationMode: 'create'`、`editKind` = `sound-effect` / `background-music`,不新增平台路由与请求体口径);「上传」复用 `upload_local_asset`。生成 / 上传成功后一律用「配对读 `(revision, manifest)`」交给 `onManifestChange`,走既有 manifest 刷新与资源投影链路,不重算依赖图、不另写布局。
|
||||
- 本地排队与进度可见:AGC 本地 durable 输出槽已按**精确动作指纹**分槽(不同 prompt / 素材名各自独立成槽,具备并行能力),但本批前端仍按「同一时刻只派发一条」排队——真并行派发需要并发收口设计(配对读 + manifest CAS + 聚焦意图互不覆盖),留待下一批;所以第一条未终态时第二条提交停在**前端本地队列**里(不调用提交 IPC,显示本地排队的「排队中。」),前一条终态后自动补发;任务状态与阶段文案(后端 `phaseDetail`)由任务账本提供,前端不拼阶段、不做百分比。进度面是**画布上常驻的可折叠任务侧栏**(位置与开合形态照抄网页端美术画布的任务侧栏的右上角锚点,颜色与外形仍走 AGC 平台 token;2026-09-21 由「左侧贴边 + 工具条入口」改为「右上角锚点 + 常驻开关」):展开是两个分栏「排队/生成中」与「已完成」(各带条数,「已完成」封顶 20 条 + 列表滚动 + 高度有界),关闭入口只保留头部那一枚 ×(底部重复的关闭按钮与其分割线已删除)、折叠即整块让出画布、只留那一枚右上角开关(工具条上不再有重复入口),开合只走画布右上角常驻的「生成任务 · N」按钮(两个页签下都在);每项显示状态徽标 / 阶段文案 / 已耗时 / 素材名,可「定位到素材」。侧栏非模态(不铺全屏遮罩、不做焦点陷阱、不参与模态遮挡判据),位置在画布右上角锚点里(照抄美术画布那一处)、**覆盖式**(不 reflow 挤窄画布视口),提交受理后自动展开。锚点按工作面分档:资源栏目画布与 UI 编辑器用画布顶边那一档,运行表现层下移让开右上角的版本入口。定位动作**每次点击都终局化**:能定位就定位并选中;素材在别的栏目先切栏目;不在投影里给「素材已不在项目里 / 已登记但尚未同步」的结论;3 秒内有界兜底,不允许提示条永久停在「正在定位生成的素材…」。
|
||||
- 本地排队与进度可见:AGC 本地 durable 输出槽已按**精确动作指纹**分槽(不同 prompt / 素材名各自独立成槽,具备并行能力),但本批前端仍按「同一时刻只派发一条」排队——真并行派发需要并发收口设计(配对读 + manifest CAS + 聚焦意图互不覆盖),留待下一批;所以第一条未终态时第二条提交停在**前端本地队列**里(不调用提交 IPC,显示本地排队的「排队中。」),前一条终态后自动补发;任务状态与阶段文案(后端 `phaseDetail`)由任务账本提供,前端不拼阶段、不做百分比。进度面是**画布上常驻的可折叠任务侧栏**(位置与开合形态照抄网页端美术画布的任务侧栏的右上角锚点,颜色与外形仍走 AGC 平台 token;2026-09-21 由「左侧贴边 + 工具条入口」改为「右上角锚点 + 常驻开关」):展开是两个分栏「排队/生成中」与「已完成」(各带条数,「已完成」封顶 20 条 + 列表滚动 + 高度有界),关闭入口只保留头部那一枚 ×(底部重复的关闭按钮与其分割线已删除)、折叠即整块让出画布、只留那一枚右上角开关(工具条上不再有重复入口),开合只走画布右上角那一枚「生成任务 · N」开关(两个页签下都在;**展开后开关让位、只留面板**,收起走面板头部 × 或点画布外部);锚点是画布那一格网格里的条目(不是写死 `top` 的绝对定位),工具条换行变高也不会压上去;每项显示状态徽标 / 阶段文案 / 已耗时 / 素材名,可「定位到素材」。侧栏非模态(不铺全屏遮罩、不做焦点陷阱、不参与模态遮挡判据),位置在画布右上角锚点里(照抄美术画布那一处)、**覆盖式**(不 reflow 挤窄画布视口),提交受理后自动展开。锚点按工作面分档:资源栏目画布与 UI 编辑器用画布顶边那一档,运行表现层下移让开右上角的版本入口;锚点是**画布那一格网格里的条目**(不是写死 `top` 的绝对定位),所以工具条换行变高也不会压上去。定位动作**每次点击都终局化**:能定位就定位并选中;素材在别的栏目先切栏目;不在投影里给「素材已不在项目里 / 已登记但尚未同步」的结论;3 秒内有界兜底,不允许提示条永久停在「正在定位生成的素材…」。
|
||||
- 面板形态:独立浮层(`ThemedModal`),**不在当前面板下面追加内容**;面板内不写功能说明或规则解释文案。**点「生成」即同步关闭面板**(不等 IPC、不等排队、不等生成),面板里**不出现**「排队中。」「正在生成。」「提交中…」这类阶段文案——阶段文案的唯一去处是任务侧栏与工具栏提示条。**只有「点击瞬间就失败」**(校验不过、权限拒绝、start IPC 立即报错)才自动重开面板并带回草稿与原因;**受理之后才失败**只在侧栏把该任务收口为失败 + 原因,不重开面板。关闭 ≠ 取消请求(请求挂在任务与账本上,不挂在面板生命周期上)。
|
||||
- 参数口径:比例 / 尺寸选项来自网页端美术画布的纯模型(`src/components/image-editor/ImageCanvasGenerationModel.ts`),并按本地 IPC 白名单收窄(本地通道明确拒绝 `4:3`);默认档 `1:1 · 1K`,生成 UI 设计图沿用网页端 UI 设计面板的默认 `16:9 · 1K`。本地 IPC 没有 `model` 入参,因此面板**不渲染模型选择器**(渲染一个改不了请求的控件就是假控件)。
|
||||
- 规范类和固定档:生成规范下的图标规范 / 角色规范 / 自定义规范都是固定档——只读展示当前规格,不给点了不生效的比例控件。本地 IPC 只有一条规范通道(`spec` 在 Rust 侧收口到 `icon-spec`),**没有 `specType` 入参**,因此角色规范与自定义规范共用该通道,靠素材名与提示词区分。
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# 决策记录
|
||||
|
||||
## 2026-09-21 验收现场修正:任务开关压在工具条上、开关与面板并排
|
||||
|
||||
- 背景:客户端验收截图两条:①画布右上角那枚「生成任务 · N」开关与工具条(打开项目目录 / 资源面板 / 整理画布 / 管理未完成编辑 / 依赖 · 类型)**重叠**;②「生成任务」面板展开时,开关与面板**并排**摆着,产品口径是「这俩不应该并排,出来详情以后入口就应该隐藏」。
|
||||
- 决策(重叠):锚点不再用 `position: absolute` + 写死的 `top: 3.5rem`(工具条是 stage 第一行,高度随按钮换行变化,写死的偏移迟早压上去),改成 **stage 网格里与工作面同一个单元格的另一个条目**:`grid-row` 按 `placement` 分档(资源画布 3 / UI 编辑器 2 / 运行表现层 `2 / -1`)+ `grid-column: 1` + `justify-self: end` + `align-self: start` + `margin: 0.85rem`(运行那档上边距 3.5rem 让开右上角版本入口)。为此把同格的三处容器也显式钉住第 1 列(`.game-workbench-stage > .game-resource-manager`、`.…[data-resource-view-state='resources.ui-editor'] > .game-workbench-editor-shell`、`.game-run-surface`)——锚点是显式定位条目,画布若走自动列放置会被挤进隐式第二列、画布直接压窄一半。
|
||||
- 决策(并排):开关只在**完全收起**时渲染(`!open && phase === 'idle'`,收起动画期间也不画,否则那 160ms 又会同框);展开态画布右上角只有面板,收起走面板头部那枚 × 或点画布外部。原来「点画布外部自动收起」的判据里对开合按钮的排除保留(收起态仍靠它开合)。
|
||||
- 原因:验收口径优先于「照抄美术画布」——美术画布把开关常驻在面板旁边,但产品要的是「入口与详情不同时出现」。重叠那条的根因是**猜了一个绝对偏移量**:工具条高度不是常量,锚点必须由布局自己推导。
|
||||
- 验证:`resourceCanvasAssetGenerationTasksPanel.test.tsx` 12 passed(新增「展开时开关让位:同一时刻只有面板那枚 ×」;计数用例改为收起态查开关、展开态查面板头部)、`resourceCanvasAssetGenerationTasksSidebarStyle.test.ts` 11 passed(锚点改成网格条目坐标:`grid-row` / `grid-column` / `justify-self` / `align-self` / `margin` 与运行档上边距;窄屏改判 `justify-self: stretch`)、`resourceCanvasGenerationTasksSidebarDismiss.test.tsx` 5 passed(收起改走面板 ×、收起后开关回来能再打开、锚点仍在 stage 里)、`resourceCanvasAssetGenerationBackgroundClose.test.tsx` 5 passed;`appSurface.test.ts` 538 passed / 17 skipped / 0 failed。
|
||||
- 影响范围:`apps/ai-game-creator-shell/src/features/resource-canvas/{ResourceCanvasAssetGenerationTasksPanelView.tsx,resourceCanvasAssetGenerationTasksSidebar.css}`、`apps/ai-game-creator-shell/src/styles.css`、三个同场景用例文件、PRD §3.10 与更新时间。
|
||||
- 已知未覆盖:仍无真机目视(网格落点在 Tauri 里的实际观感、工具条换行时锚点是否仍贴画布顶边需要现场复核)。
|
||||
## 2026-09-21 AGC 替换面板改为非模态浮层,支持在画布上点选目标
|
||||
|
||||
- 背景:`docs/project-memory/todos/【待办】画布验收后续修复-2026-09-18.md` 的「后续需求」要求「在替换面板中支持画布点选目标」。2026-09-13 那版做的是「候选弹窗 footer 一个『点选替换』按钮 → 关掉弹窗 → 进画布点选态 → 点中即提交」:面板与画布互斥(弹窗外壳是全屏遮罩,留着它画布上的卡点不到),用户要么在面板里筛、要么离开面板。
|
||||
|
||||
@@ -67,9 +67,10 @@
|
||||
- 改后:新增右上角锚点 `.game-resource-generation-tasks-anchor`(`position: absolute; top: 3.5rem; right: 0.85rem; z-index: 40`,挂在 `.game-workbench-stage` 里),**开关常驻右上角、面板在开关左侧展开、收起只剩那一枚开关**(与美术画布同一形态);工具条上那两处重复入口删除;面板改由 `max-height: min(30rem, calc(100vh - 12rem))` 封顶 + 内部滚动,自己不再定位。
|
||||
- 颜色 / 圆角 / 字重 / 阴影仍全部走 `--platform-*` token(开关沿用原工具条那枚次级胶囊的外观,只多一档面板投影),没有照搬网页端的固定色值。
|
||||
- 分档坐标:view 的 `placement`(`canvas | run | editor`)落成 `data-generation-tasks-placement`;运行表现层下移到 `top: 7rem`,避开它右上角 `game-run-version-picker`(`top: 20px; right: 20px`)。
|
||||
- 本机执行过:`resourceCanvasAssetGenerationTasksPanel.test.tsx` 11 passed、`resourceCanvasAssetGenerationTasksSidebarStyle.test.ts` 11 passed、`resourceCanvasGenerationTasksSidebarDismiss.test.tsx` 5 passed、`resourceCanvasAssetGenerationBackgroundClose.test.tsx` 5 passed;仓库根 `tests/appSurface.test.ts` **538 passed / 17 skipped / 0 failed**(与既有基线逐条一致);`tsc -p apps/ai-game-creator-shell/tsconfig.json --noEmit` 通过。
|
||||
- 本机执行过:`resourceCanvasAssetGenerationTasksPanel.test.tsx` 12 passed(含验收修正后的「展开时开关让位」)、`resourceCanvasAssetGenerationTasksSidebarStyle.test.ts` 11 passed、`resourceCanvasGenerationTasksSidebarDismiss.test.tsx` 5 passed、`resourceCanvasAssetGenerationBackgroundClose.test.tsx` 5 passed;仓库根 `tests/appSurface.test.ts` **538 passed / 17 skipped / 0 failed**(与既有基线逐条一致);`tsc -p apps/ai-game-creator-shell/tsconfig.json --noEmit` 通过。
|
||||
- **本批第二项(拖到对话批量引用)**:资源卡按住拖到右侧 Agent 对话栏、松手即按这次拖动真正参与位移的那批(多选整批、单选只引用按住的那张)插入 `@` 引用;落点铺虚线框 + 「松手即可 @ 引用 N 项素材」,拖回画布内松手仍是排版语义;引用口径与工具条「引用」按钮逐字一致(只认已登记素材、`source: resource-card`),派发走新增的批量事件一次事务插入。
|
||||
- **本批第三项(替换面板画布点选)**:替换候选面板从全屏弹窗改为**画布右上角的非模态浮层**(共享组件新增 opt-in `nonModal`,网页端弹窗行为不变),面板开着时画布照常可点——点中合法候选即落成面板里的当前选择,写入仍只由「确认」发起;非法目标在面板里报因、零写入。旧的「点选替换」按钮、画布提示条与点选态随之退役(同一个功能不留两条 UI 路径)。
|
||||
- **验收现场修正(第四批内)**:截图两条——右上角「生成任务」开关与工具条**重叠**、展开时开关与面板**并排**。锚点改挂**画布那一格网格**(不再用写死 `top` 的绝对定位,工具条换行也不会压上去),开关只在完全收起时渲染(展开后由面板头部 × 或点外部收起)。
|
||||
- 本机执行过(三项合计):`resourceCanvasAssetGenerationTasksPanel` / `…SidebarStyle` / `…SidebarDismiss` / `…BackgroundClose`、`resourceCardReferenceDropModel`、`resourceCanvasChatReferenceDrop`、`resourceReferenceInput`、`resourceVersionReplacement`、`projectAssetPickerDialogShellStyle`、`ImageCanvasEditorView`、`ImageCanvasEditorGenerationIntegration`、`projectResourceLiveIntegration`、`appSurface` 全绿(定向批次多项合计 694 passed / 17 skipped);`typecheck`、`check:encoding`、`git diff --check`、prettier / eslint 通过。
|
||||
- 仍未做:真实客户端目视(三项的真机手感与落点)与远程 CI 都没跑;窄屏(≤480px)只有声明级断言;「复制对话保留有效引用」未做。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user