恢复被 #193 误删的策划前端全部样式,并加上组件到样式的守门测试
症状:立项策划界面整体裸奔——审批卡、阶段进度条、交付行没有任何框和布局, 「查看 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:
@@ -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,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user