feat(space): route workbench execution items

This commit is contained in:
Albert 2026-04-26 10:56:01 +08:00
parent a7008bb3cc
commit 6c0f6dac53
3 changed files with 100 additions and 3 deletions

View File

@ -165,6 +165,10 @@ function projectHref(projectId: string): string {
return `/pmflow-os?projectId=${encodeURIComponent(projectId)}`;
}
function executionHref(projectId: string): string {
return `/execution-bridge?projectId=${encodeURIComponent(projectId)}`;
}
function pendingChangeItems(truth: ProjectTruthState | null): ChangeItem[] {
if (!truth) return [];
return truth.changeItems.filter((item) => item.status === "pending_review");
@ -249,7 +253,7 @@ function deriveAIProgress(snapshot: ProjectSnapshot): SpaceAIProgressItem[] {
const { project, truth, execution } = snapshot;
const projectId = String(project.id);
const projectTitle = project.title || truth?.project.name || `项目 ${projectId}`;
const href = projectHref(projectId);
const href = executionHref(projectId);
const items: SpaceAIProgressItem[] = [];
if (execution && ACTIVE_EXECUTION_STATUSES.includes(execution.execution.status)) {

View File

@ -47,4 +47,97 @@ describe("PersonalSpacePage", () => {
expect(screen.getByText("暂存想法只读聚合尚未接入后端合同,当前不使用 mock 数据冒充真实内容。")).toBeInTheDocument();
expect(screen.queryByText("AI 周报助手")).not.toBeInTheDocument();
});
it("keeps workbench actions as navigation-only links into the main flows", async () => {
vi.mocked(spaceApi.getWorkbench).mockResolvedValue({
...emptyWorkbench,
projects: [
{
id: "17",
title: "真实项目",
summary: "真实项目摘要",
status: "进行中",
stage: "开发",
progress: 62,
pendingCount: 1,
activeTaskCount: 2,
riskLabel: "中",
nextAction: "继续推进核心任务",
updatedAt: "2026-04-26T08:00:00.000Z",
href: "/pmflow-os?projectId=17",
},
],
pendingConfirmations: [
{
id: "change-91",
projectId: "17",
projectTitle: "真实项目",
title: "确认需求变更",
summary: "等待在主流程确认变更。",
source: "changeset",
stage: "需求",
riskLevel: "medium",
createdAt: "2026-04-26T08:00:00.000Z",
href: "/change-review?projectId=17&changeSetId=91",
},
],
aiProgress: [
{
id: "execution-17",
projectId: "17",
projectTitle: "真实项目",
title: "AI 正在推进执行链路",
summary: "正在聚合执行进度与回写状态。",
status: "coding",
progress: 52,
updatedAt: "2026-04-26T08:00:00.000Z",
href: "/execution-bridge?projectId=17",
},
],
drafts: [
{
id: "17-需求-draft-1",
projectId: "17",
projectTitle: "真实项目",
title: "待确认草案",
summary: "待确认草案摘要",
objectType: "需求",
status: "draft",
updatedAt: "2026-04-26T08:00:00.000Z",
href: "/pmflow-os?projectId=17",
},
],
recentProjects: [
{
id: "17",
title: "真实项目",
summary: "真实项目摘要",
status: "进行中",
stage: "开发",
progress: 62,
pendingCount: 1,
activeTaskCount: 2,
riskLabel: "中",
nextAction: "继续推进核心任务",
updatedAt: "2026-04-26T08:00:00.000Z",
href: "/pmflow-os?projectId=17",
},
],
});
renderWithProviders(<PersonalSpacePage />, ["/space"]);
expect(await screen.findByRole("link", { name: /进入主流程/ })).toHaveAttribute(
"href",
"/pmflow-os?projectId=17"
);
expect(screen.getAllByRole("link", { name: /确认需求变更/ })[0]).toHaveAttribute(
"href",
"/change-review?projectId=17&changeSetId=91"
);
expect(screen.getAllByRole("link", { name: /AI 正在推进执行链路/ })[0]).toHaveAttribute(
"href",
"/execution-bridge?projectId=17"
);
});
});

View File

@ -299,13 +299,13 @@ function WorkbenchHeader({
</div>
<h1 className="mt-2 text-3xl font-semibold tracking-tight text-slate-950">个人工作面</h1>
<p className="mt-2 text-sm leading-6 text-slate-500">
聚合继续推进、待确认、AI 正在推进、暂存想法和草案箱;点击项目后回到主流程处理。
聚合继续推进、待确认、执行中、暂存想法、草案箱和最近项目;点击项目后回到主流程处理。
</p>
</div>
<div className="grid min-w-[420px] grid-cols-3 gap-3">
<MetricTile label="最近项目" value={projectCount} />
<MetricTile label="待确认" value={pendingCount} tone="orange" />
<MetricTile label="AI 推进" value={aiCount} tone="blue" />
<MetricTile label="执行中" value={aiCount} tone="blue" />
</div>
</div>
<div className="mt-4 flex items-center gap-2 text-xs text-slate-500">