恢复被 #193 误删的策划前端全部样式,并加上组件到样式的守门测试
Project CI / Repository checks (pull_request) Successful in 3m29s
Project CI / Frontend tests (pull_request) Successful in 4m27s
Project CI / Backend tests (pull_request) Successful in 5m44s
Project CI / Native shell tests (pull_request) Successful in 12m53s

症状:立项策划界面整体裸奔——审批卡、阶段进度条、交付行没有任何框和布局,
「查看 GDD 正文」弹层因 backdrop 丢掉 fixed 定位而内联平铺进消息流。

成因:策划前端经 squash PR #159 完整进入 master,组件(TSX)至今与源分支最终态
逐字节一致;随后 #193「Codex/agent chat layout fix」重写聊天区样式时,把
styles.css 里策划前端的选择器整段删除——gdd-approval-card 34 条、plan-gdd* 17
条、planning-lane-runtime-strip 上限——未搬往任何其他文件(全仓检索为 0)。同一
个 PR 把 project-development.suite.ts 大幅重写,原有的 CSS 守门断言一并消失,
所以没有任何测试变红。

修复:从 5802048c3^ 原样取回全部 42 个规则块,整段追加在 styles.css 末尾并注明
来历。这些选择器在当前文件中出现次数为 0,纯加法、零冲突,不触碰 #193/#194 有意
重构的聊天区规则;追加在末尾也保持了原有的同权重覆盖关系(策划窄条的上限仍压过
调试面板的 240px 天花板)。

守门:plan-gdd.suite.ts(两轮样式重构都未被触碰的文件)新增一条测试,类名清单
直接从 GddApprovalCard / PlanningLaneRuntimeStrip 源码推导,逐个断言 styles.css
里有对应规则,另单独锁弹层 backdrop 的 fixed 定位。A/B 验证:抽掉恢复段即红,报
「styles.css 缺少 .plan-gdd-surface 的规则」。组件加新类而样式缺失、或样式再次
被顺手清理,这条都会拦住。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-25 07:55:22 +00:00
parent c09a511aaa
commit d170326a6b
2 changed files with 359 additions and 0 deletions
+310
View File
@@ -7209,3 +7209,313 @@ iframe.preview-frame {
grid-column: 1 / -1;
}
}
/* ============================================================
立项策划前端样式(PlanGddSurface / 阶段进度 / GDD 审批卡 / 交付行 /
planning-lane-runtime-strip)。
这一段曾在 #193「Codex/agent chat layout fix」重写聊天样式时被整体误删:
组件(TSX)原样保留、选择器全数消失,策划界面裸奔、正文弹层失去 fixed 定位
变成内联平铺。从 5802048c3^ 原样恢复。聊天区样式重构时请勿顺手清理本段——
这些选择器的使用方在 features/project-workspace/GddApprovalCard.tsx 与
PlanningLaneRuntimeStrip.tsx。
============================================================ */
/* 策划区是一个面:阶段进度是标题栏,审批卡是正文。边框和圆角只画在外壳上,里面两块
不再各自带框。 */
.plan-gdd-surface{
display: grid;
min-width: 0;
border: 1px solid #cfd7e6;
border-radius: 10px;
background: #fff;
overflow: hidden;
}
.gdd-approval-card{
display: grid;
gap: 14px;
padding: 16px 18px 18px;
background: #f8fbff;
}
.plan-gdd-surface--with-card .plan-gdd-stage-progress{
border-bottom: 1px solid #dbe4f1;
}
.plan-gdd-stage-progress{
display: grid;
gap: 6px;
padding: 10px 12px;
background: #fff;
color: #526173;
font-size: 12px;
}
.plan-gdd-stage-progress__header,
.plan-gdd-stage-progress__meta{
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 8px 14px;
}
/* 批准后的交付行。它是策划阶段唯一的产物出口,所以给一条分隔线把它和上面的状态
区分开,而不是混成第三行元信息。 */
.plan-gdd-stage-progress__delivery{
display: grid;
gap: 8px;
margin-top: 4px;
padding-top: 9px;
border-top: 1px solid #e6ecf5;
}
.plan-gdd-stage-progress__delivery code{
min-width: 0;
color: #3c4a5c;
font-size: 11px;
overflow-wrap: anywhere;
}
.plan-gdd-stage-progress__delivery-actions{
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.plan-gdd-stage-progress__delivery-actions button{
min-height: 30px;
padding: 0 12px;
border: 1px solid #cfd7e6;
border-radius: 6px;
color: #27364a;
background: #fff;
font-size: 12px;
}
.plan-gdd-stage-progress__delivery-actions button:hover:not(:disabled),
.plan-gdd-stage-progress__delivery-actions button:focus-visible{
border-color: #1f6feb;
color: #1f6feb;
}
.plan-gdd-stage-progress__delivery-actions button:disabled{
opacity: 0.58;
}
.plan-gdd-stage-progress__delivery-error{
color: #b42323;
overflow-wrap: anywhere;
}
.plan-gdd-stage-progress__header strong{
color: #27364a;
}
.plan-gdd-stage-progress__header span{
padding: 3px 8px;
border-radius: 999px;
background: #eef6ff;
color: #1f6feb;
}
.gdd-approval-card__header{
display: grid;
gap: 5px;
min-width: 0;
}
.gdd-approval-card h2,
.gdd-approval-card h3,
.gdd-approval-card p{
margin: 0;
}
.gdd-approval-card h2{
font-size: 18px;
}
.gdd-approval-card__header p{
color: #526173;
overflow-wrap: anywhere;
}
.gdd-approval-card__details-trace{
color: #6b7a8c;
font-size: 11px;
overflow-wrap: anywhere;
}
.gdd-approval-card__details-trigger{
justify-self: start;
min-height: 30px;
padding: 0 10px;
border: 1px solid #cfd7e6;
border-radius: 6px;
color: #27364a;
background: #fff;
}
.gdd-approval-card__decisions{
display: grid;
gap: 8px;
}
.gdd-approval-card__decisions article{
display: grid;
gap: 3px;
padding: 9px 10px;
border: 1px solid #e2e8f0;
border-radius: 7px;
background: #fff;
}
.gdd-approval-card__decisions article span{
color: #1f6feb;
font-size: 12px;
}
.gdd-approval-card__decisions article small{
color: #526173;
overflow-wrap: anywhere;
}
.gdd-approval-card__actions,
.gdd-approval-card__dialog-actions,
.gdd-approval-card__recovery{
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
}
.gdd-approval-card__actions button,
.gdd-approval-card__dialog-actions button,
.gdd-approval-card__recovery button{
min-height: 32px;
padding: 0 12px;
border: 1px solid #cfd7e6;
border-radius: 6px;
color: #fff;
background: #1f6feb;
}
.gdd-approval-card__actions button:nth-child(n + 2),
.gdd-approval-card__dialog-actions button:first-child{
color: #27364a;
background: #fff;
}
.gdd-approval-card button:disabled{
cursor: not-allowed;
opacity: 0.55;
}
.gdd-approval-card__recovery{
justify-content: space-between;
padding: 9px 10px;
border-radius: 7px;
color: #854d0e;
background: #fff7df;
font-size: 13px;
}
.gdd-approval-card__error{
padding: 9px 10px;
border-radius: 7px;
color: #991b1b;
background: #fff1f2;
overflow-wrap: anywhere;
}
.gdd-approval-card__dialog-backdrop{
position: fixed;
inset: 0;
z-index: 220;
display: grid;
padding: 24px;
background: rgb(0 0 0 / 56%);
place-items: center;
}
.gdd-approval-card__dialog{
display: grid;
gap: 12px;
width: min(520px, 100%);
padding: 20px;
border: 1px solid #e5e7eb;
border-radius: 8px;
background: #fff;
box-shadow: 0 18px 52px rgb(0 0 0 / 18%);
}
.gdd-approval-card__details{
max-height: min(760px, calc(100vh - 32px));
overflow: auto;
}
.gdd-approval-card__details header,
.gdd-approval-card__details article{
display: grid;
gap: 5px;
}
.gdd-approval-card__details article{
padding-top: 10px;
border-top: 1px solid #e2e8f0;
}
.gdd-approval-card__details p{
white-space: pre-line;
overflow-wrap: anywhere;
}
.gdd-approval-card__dialog textarea{
width: 100%;
min-height: 110px;
padding: 9px;
border: 1px solid #cfd7e6;
border-radius: 6px;
resize: vertical;
}
.gdd-approval-card__dialog-hint{
padding: 9px 10px;
border-radius: 7px;
color: #854d0e;
background: #fff7df;
font-size: 13px;
}
/* 审批卡是这一列里唯一没有天花板的成员:Fast GDD 的决定项越多它越高,能把消息列表和运行
时面板一起挤出可视区。和 `.agent-runtime-status` 一样给它自己的上限加内部滚动,而不是
让它去挤别人。 */
/* 这个面不参与 flex 压缩。它自己不会长高:标题栏是固定几行,审批卡下面有自己的
max-height 和内滚,所以高度天然有界。让它可压缩的话,`overflow: hidden`(画圆角
要的)会把底部切掉——批准后交付行正好在那儿,表现是路径和两个按钮凭空消失。 */
.game-workbench-chat .plan-gdd-surface{
flex: 0 0 auto;
}
/* 只有审批卡在场时才需要「标题栏定高 + 正文占余下」的两行轨道。批准后卡片收掉,
面里只剩标题栏,这条声明会给它套上一个不存在的第二行。 */
.game-workbench-chat .plan-gdd-surface--with-card{
min-height: 0;
grid-template-rows: auto minmax(0, 1fr);
}
.game-workbench-chat .gdd-approval-card{
min-height: 0;
max-height: clamp(240px, 52dvh, 640px);
overflow-y: auto;
overscroll-behavior: contain;
scrollbar-gutter: stable;
}
/* 上面 24dvh/240px 的天花板是给做游戏链路常驻的调试面板设的。策划链路的窄条只在
需要用户动手时出现(澄清卡 / 失败恢复),内容是要读完再回答的题面,给它和审批
卡同级的空间。 */
.game-workbench-chat .planning-lane-runtime-strip{
max-height: clamp(240px, 52dvh, 640px);
}
@@ -1,3 +1,6 @@
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import {
agentRuntimeUserInputRequest,
createPlanGddStateView,
@@ -521,4 +524,50 @@ export function registerPlanGddApprovalTests() {
expectSupervisorRuntimePanelAbsent();
expect(screen.queryByText('策划子 Run 退出')).toBeNull();
});
it('keeps a stylesheet rule for every class the planning components reference', () => {
// #193「Codex/agent chat layout fix」重写聊天样式时,把策划前端的选择器整段
// 误删:组件(TSX)原样保留、样式全数消失,审批卡/阶段条/交付行裸奔,正文弹层
// 失去 fixed 定位变成内联平铺。组件和它的样式分居两个文件,重构样式的人看不见
// 使用方——这条测试就是那根缺失的连线:类名清单直接从组件源码里推导,组件加了
// 新类而样式没跟上、或样式又被顺手清掉,这里都会红。
const componentSources = [
'src/features/project-workspace/GddApprovalCard.tsx',
'src/features/project-workspace/PlanningLaneRuntimeStrip.tsx',
]
.map((path) =>
readFileSync(
resolve(process.cwd(), 'apps/ai-game-creator-shell', path),
'utf8',
),
)
.join('\n');
const referencedClasses = new Set<string>();
for (const match of componentSources.matchAll(
/className=(?:"([^"]+)"|\{`([^`]+)`\})/g,
)) {
const literal = (match[1] ?? match[2] ?? '').replace(/\$\{[^}]*\}/g, ' ');
for (const name of literal.split(/\s+/)) {
if (name) {
referencedClasses.add(name);
}
}
}
expect(referencedClasses.size).toBeGreaterThan(10);
const styles = readFileSync(
resolve(process.cwd(), 'apps/ai-game-creator-shell/src/styles.css'),
'utf8',
);
for (const name of referencedClasses) {
expect(
styles.includes(`.${name}`),
`styles.css 缺少 .${name} 的规则`,
).toBe(true);
}
// 正文弹层必须是浮层:backdrop 一旦丢掉 fixed 定位,整个 GDD 会内联平铺进
// 消息流里——这正是误删当时最刺眼的症状。
expect(styles).toMatch(
/\.gdd-approval-card__dialog-backdrop\s*\{[^}]*position:\s*fixed/s,
);
});
}