策划区合成一个面:阶段进度当标题栏,审批卡头去掉重复的状态与指纹
阶段进度条和审批卡原本各带一圈边框叠在一起。「立项策划 / 待审批」在进度条和卡头 各画一遍,「v1」在进度条、卡片 meta 行和批准按钮上出现三次,卡头还露一截 12 位的 指纹——那是开发者核对用的,用户不需要。 新增 PlanGddSurface 做外壳:边框和圆角只画在它上面,进度条成为标题栏,审批卡成为 正文,卡头只剩游戏标题和一句话。meta 行整行删掉:版本在标题栏和按钮上都有,决定 数就是下面那张清单本身,指纹整条搬进「查看 GDD 正文」弹层底部做追溯。两个挂载点 改成只挂这一个组件,可见性判据抽成两个小函数供外壳和两块子件共用,不再各自拼装。 做游戏工作台原先给卡片设的高度上限和内部滚动挪到外壳为 flex 子件、卡片为可滚行, 行为不变。 新增一条 appSurface 用例(A/B:换回旧卡片即红):状态与版本只在标题栏;卡内没有 「立项策划」「待审批」「版本 v1」「指纹」;弹层底部有完整版本与指纹。 styles.css 在基线就不符合 prettier,本次只改相关规则,不整文件重排。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,60 @@ const stateLabels: Record<PlanGddStateViewV1['state'], string> = {
|
||||
rejected: '已退回',
|
||||
};
|
||||
|
||||
function stageProgressVisible(
|
||||
state: PlanGddStateViewV1 | null,
|
||||
active: boolean,
|
||||
) {
|
||||
return Boolean(state && (active || state.state !== 'not_started'));
|
||||
}
|
||||
|
||||
function approvalCardVisible(state: PlanGddStateViewV1 | null) {
|
||||
return Boolean(
|
||||
state?.displayGdd && (state.pendingApproval || state.recoveryPending),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 策划区的外壳:阶段进度是它的标题栏,审批卡是它的正文。
|
||||
*
|
||||
* 两者原本各画一个带边框的盒子,叠在一起时「立项策划 / 状态」在标题栏和卡头各出现
|
||||
* 一次。合成一个面之后卡头只剩游戏标题和一句话,状态与版本只在标题栏画一遍。两个
|
||||
* 挂载点都从这里进,不再各自拼装。
|
||||
*/
|
||||
export function PlanGddSurface({
|
||||
state,
|
||||
active = false,
|
||||
busy,
|
||||
error,
|
||||
onRefresh,
|
||||
onDecision,
|
||||
}: GddApprovalCardProps & { active?: boolean }) {
|
||||
const showProgress = stageProgressVisible(state, active);
|
||||
const showCard = approvalCardVisible(state);
|
||||
if (!showProgress && !showCard) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<section
|
||||
className={`plan-gdd-surface${showCard ? ' plan-gdd-surface--with-card' : ''}`}
|
||||
aria-label="立项策划"
|
||||
>
|
||||
{showProgress ? (
|
||||
<PlanGddStageProgress state={state} active={active} />
|
||||
) : null}
|
||||
{showCard ? (
|
||||
<GddApprovalCard
|
||||
state={state}
|
||||
busy={busy}
|
||||
error={error}
|
||||
onRefresh={onRefresh}
|
||||
onDecision={onDecision}
|
||||
/>
|
||||
) : null}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export function PlanGddStageProgress({
|
||||
state,
|
||||
active = false,
|
||||
@@ -32,7 +86,7 @@ export function PlanGddStageProgress({
|
||||
state: PlanGddStateViewV1 | null;
|
||||
active?: boolean;
|
||||
}) {
|
||||
if (!state || (!active && state.state === 'not_started')) {
|
||||
if (!state || !stageProgressVisible(state, active)) {
|
||||
return null;
|
||||
}
|
||||
const latestVersion =
|
||||
@@ -156,10 +210,7 @@ export function GddApprovalCard({
|
||||
const [comment, setComment] = useState('');
|
||||
const [gddDetailsOpen, setGddDetailsOpen] = useState(false);
|
||||
|
||||
if (
|
||||
!state?.displayGdd ||
|
||||
(!state.pendingApproval && !state.recoveryPending)
|
||||
) {
|
||||
if (!state?.displayGdd || !approvalCardVisible(state)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -187,22 +238,10 @@ export function GddApprovalCard({
|
||||
return (
|
||||
<section className="gdd-approval-card" aria-label="GDD 审批卡">
|
||||
<header className="gdd-approval-card__header">
|
||||
<div>
|
||||
<span className="gdd-approval-card__eyebrow">立项策划</span>
|
||||
<h2>{gdd.game.title || `Fast GDD v${gdd.version}`}</h2>
|
||||
<p>{gdd.game.oneLiner}</p>
|
||||
</div>
|
||||
<span className="gdd-approval-card__state">
|
||||
{stateLabels[state.state]}
|
||||
</span>
|
||||
<h2>{gdd.game.title || `Fast GDD v${gdd.version}`}</h2>
|
||||
<p>{gdd.game.oneLiner}</p>
|
||||
</header>
|
||||
|
||||
<div className="gdd-approval-card__meta">
|
||||
<span>{`版本 v${gdd.version}`}</span>
|
||||
<span>{`指纹 ${gdd.fingerprint.slice(0, 12)}…`}</span>
|
||||
<span>{`${gdd.decisions.length} 项决定`}</span>
|
||||
</div>
|
||||
|
||||
{gdd.decisions.length > 0 ? (
|
||||
<div className="gdd-approval-card__decisions" aria-label="决定状态">
|
||||
{gdd.decisions.map((decision) => (
|
||||
@@ -349,6 +388,12 @@ export function GddApprovalCard({
|
||||
<p>{section.content}</p>
|
||||
</article>
|
||||
))}
|
||||
<small
|
||||
className="gdd-approval-card__details-trace"
|
||||
aria-label="GDD 版本与指纹"
|
||||
>
|
||||
{`Fast GDD v${gdd.version} · ${gdd.fingerprint}`}
|
||||
</small>
|
||||
<div className="gdd-approval-card__dialog-actions">
|
||||
<button type="button" onClick={() => setGddDetailsOpen(false)}>
|
||||
关闭
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
} from '../agent-runtime';
|
||||
import { formatAgentCardRuntimeStatus } from '../project-summary/agentPresentation';
|
||||
import { taskStatusLabels } from '../project-summary/projectSummary';
|
||||
import { GddApprovalCard, PlanGddStageProgress } from './GddApprovalCard';
|
||||
import { PlanGddSurface } from './GddApprovalCard';
|
||||
import {
|
||||
pendingCommandDetail,
|
||||
pendingCommandTitle,
|
||||
@@ -105,12 +105,9 @@ export function ProjectSupervisorView({
|
||||
aria-label={directCodex ? '陶泥儿项目对话' : '项目总控对话'}
|
||||
>
|
||||
<div className="project-supervisor-conversation">
|
||||
<PlanGddStageProgress
|
||||
<PlanGddSurface
|
||||
state={planGddState}
|
||||
active={isPlanningLaneRuntime(runtimePanelProps.runtime)}
|
||||
/>
|
||||
<GddApprovalCard
|
||||
state={planGddState}
|
||||
busy={planGddHydrateBusy}
|
||||
error={planGddError}
|
||||
onRefresh={onPlanGddRefresh}
|
||||
|
||||
+2
-5
@@ -47,7 +47,7 @@ import {
|
||||
taskGroupLabels,
|
||||
taskStatusLabels,
|
||||
} from '../project-summary/projectSummary';
|
||||
import { GddApprovalCard, PlanGddStageProgress } from './GddApprovalCard';
|
||||
import { PlanGddSurface } from './GddApprovalCard';
|
||||
import {
|
||||
pendingCommandDetail,
|
||||
pendingCommandTitle,
|
||||
@@ -365,12 +365,9 @@ export function ProjectWorkspaceChatPane({
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<PlanGddStageProgress
|
||||
<PlanGddSurface
|
||||
state={planGddState}
|
||||
active={isPlanningLaneRuntime(projectSupervisorRuntime)}
|
||||
/>
|
||||
<GddApprovalCard
|
||||
state={planGddState}
|
||||
busy={planGddHydrateBusy}
|
||||
error={planGddError}
|
||||
onRefresh={onPlanGddRefresh}
|
||||
|
||||
@@ -3448,21 +3448,32 @@ textarea {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* 策划区是一个面:阶段进度是标题栏,审批卡是正文。边框和圆角只画在外壳上,里面两块
|
||||
不再各自带框。 */
|
||||
.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: 18px;
|
||||
border: 1px solid #cfd7e6;
|
||||
border-radius: 10px;
|
||||
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;
|
||||
border: 1px solid #dbe4f1;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
color: #526173;
|
||||
font-size: 12px;
|
||||
@@ -3489,15 +3500,9 @@ textarea {
|
||||
}
|
||||
|
||||
.gdd-approval-card__header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.gdd-approval-card__eyebrow {
|
||||
color: #526173;
|
||||
font-size: 12px;
|
||||
display: grid;
|
||||
gap: 5px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.gdd-approval-card h2,
|
||||
@@ -3507,31 +3512,18 @@ textarea {
|
||||
}
|
||||
|
||||
.gdd-approval-card h2 {
|
||||
margin-top: 3px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.gdd-approval-card__header p {
|
||||
margin-top: 5px;
|
||||
color: #526173;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.gdd-approval-card__state {
|
||||
flex: 0 0 auto;
|
||||
padding: 4px 8px;
|
||||
border-radius: 999px;
|
||||
color: #0c4a6e;
|
||||
background: #dff3ff;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.gdd-approval-card__meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 14px;
|
||||
color: #526173;
|
||||
font-size: 12px;
|
||||
.gdd-approval-card__details-trace {
|
||||
color: #6b7a8c;
|
||||
font-size: 11px;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.gdd-approval-card__details-trigger {
|
||||
@@ -6529,8 +6521,14 @@ iframe.preview-frame {
|
||||
/* 审批卡是这一列里唯一没有天花板的成员:Fast GDD 的决定项越多它越高,能把消息列表和运行
|
||||
时面板一起挤出可视区。和 `.agent-runtime-status` 一样给它自己的上限加内部滚动,而不是
|
||||
让它去挤别人。 */
|
||||
.game-workbench-chat .gdd-approval-card {
|
||||
.game-workbench-chat .plan-gdd-surface {
|
||||
flex: 0 1 auto;
|
||||
min-height: 0;
|
||||
grid-template-rows: auto minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.game-workbench-chat .gdd-approval-card {
|
||||
min-height: 0;
|
||||
max-height: clamp(160px, 34dvh, 380px);
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
|
||||
@@ -276,6 +276,38 @@ export function registerPlanGddApprovalTests() {
|
||||
expect(screen.getByRole('button', { name: '批准 v1' })).not.toBeNull();
|
||||
});
|
||||
|
||||
it('paints the planning state once in the stage strip and keeps the fingerprint inside the GDD dialog', async () => {
|
||||
// 阶段进度和审批卡曾各自带框叠在一起,「立项策划 / 待审批」在标题栏和卡头各画一遍,
|
||||
// 卡头还露一截指纹。合成一个面之后:状态与版本只在标题栏,卡头只剩游戏标题和一句
|
||||
// 话,指纹整条放进正文弹层做追溯。
|
||||
const harness = createProjectSupervisorRuntimeHarness();
|
||||
const view = createPlanGddStateView();
|
||||
harness.setPlanGddState(view);
|
||||
const card = await mountApprovalCard(harness);
|
||||
|
||||
const surface = screen.getByLabelText('立项策划');
|
||||
const strip = within(surface).getByLabelText('立项策划阶段进度');
|
||||
expect(within(strip).getByText('待审批')).not.toBeNull();
|
||||
expect(within(strip).getByText('当前版本:v1')).not.toBeNull();
|
||||
expect(surface.contains(card)).toBe(true);
|
||||
|
||||
expect(within(card).queryByText('立项策划')).toBeNull();
|
||||
expect(within(card).queryByText('待审批')).toBeNull();
|
||||
expect(within(card).queryByText(/^版本 v1$/)).toBeNull();
|
||||
expect(within(card).queryByText(/指纹/)).toBeNull();
|
||||
expect(within(card).getByRole('heading', { level: 2 }).textContent).toBe(
|
||||
view.displayGdd?.game.title,
|
||||
);
|
||||
|
||||
fireEvent.click(
|
||||
within(card).getByRole('button', { name: '查看 GDD 正文' }),
|
||||
);
|
||||
const dialog = screen.getByRole('dialog');
|
||||
expect(within(dialog).getByLabelText('GDD 版本与指纹').textContent).toBe(
|
||||
`Fast GDD v1 · ${view.displayGdd?.fingerprint}`,
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps the supervisor runtime panel off the planning lane while the planner is working', async () => {
|
||||
// 完整面板是给做游戏链路的:十几个专业 Agent、多步计划、逐 Agent 重试。策划链路
|
||||
// 只有一个 project-planning 子 Run、一两步计划,面板画出来的全是 D11 拓扑的内部
|
||||
|
||||
Reference in New Issue
Block a user