feat(space): surface review attention queue
This commit is contained in:
parent
e8bb125608
commit
0bd9c1ddf7
|
|
@ -132,7 +132,7 @@ frontend/
|
|||
- 页面目录化、Mounted AI Console、Inbox、AI Steward 与 Recipes/Projects/Users 等模块化拆分的详细库存见库存文档。
|
||||
- 顶层路由已改为 `React.lazy + Suspense`,统一 fallback 使用 `frontend/src/components/layout/LazyBoundary.tsx`;AI Steward / AI Config 页面按 active tab 只渲染当前可见区块,Projects/Users/图表页则把重对话框与图表从同步入口拆出。
|
||||
- `frontend/src/features/project-launch/` 是新项目启动台独立产品域:`types.ts / useProjectLaunchState.ts` 承载状态合同和真实 API 编排,`mockData.ts` 只在显式 demo 模式下使用;默认路径调用 `POST /pmflow-launch/drafts` 生成可审查草案,确认后调用 `POST /pmflow-launch/drafts/{draft_id}/confirm` 创建真实项目并跳转 `/pmflow-os?projectId=<id>`。`LaunchShell` 桌面端复用 `frontend/src/features/personal-space/components/SpaceChrome.tsx` 的空间左侧栏和顶部条,主画布仍由 `IntentInputPanel / AIProgressCanvas / HumanReviewCanvas / DraftEditCanvas / FailureStatePanel / SuccessTransitionPanel` 承接,移动端仍由 `MobileLaunchLayout` 承接;`frontend/src/pages/ProjectLaunchPage.tsx` 仅做薄入口。
|
||||
- `frontend/src/features/personal-space/` 是空间层入口:`PersonalSpacePage.tsx` 承接桌面个人工作面与独立移动单手布局,`frontend/src/api/space.ts` 只读聚合 `projects` summary、PMFlow OS `truth-state`、`execution-summary` 并生成 `nextActionQueue` 我的下一步队列、继续推进、待确认、发布关注、发布后反馈关注、下一轮迭代草稿关注、AI 正在推进、草案箱、最近项目摘要;Space P6 的下一步队列按高风险待确认、发布阻塞/失败证据、handoff consumed 后缺少后续 progress、下一轮 draft、发布后反馈/收入/合规信号排序,点击只跳 `/change-review?projectId=<id>&changeSetId=<id>`、`/execution-bridge?projectId=<id>` 或 `/release-growth?projectId=<id>`;Space P3 的发布关注优先消费 `truth-state.releases` 的 release validation 投影,没有验证投影时才回落 `execution-summary.releaseGate`;Space P4/P5 的发布后反馈/下一轮草稿关注只读消费 `execution-summary.releaseSignals`;不在 Space 写 Project Memory、ProgressEvent、ReleaseValidationRun、FeedbackIngest、RevenueEvent、ComplianceCheckRun、ChangeSet 状态或发布执行。`components/SpaceChrome.tsx` 承接空间层左侧栏、顶部条与空间用量组件,并被 `/project-launch` 桌面首次新建壳复用;`frontend/src/pages/PersonalSpacePage.tsx` 仅做薄入口。
|
||||
- `frontend/src/features/personal-space/` 是空间层入口:`PersonalSpacePage.tsx` 承接桌面个人工作面与独立移动单手布局,`frontend/src/api/space.ts` 只读聚合 `projects` summary、PMFlow OS `truth-state`、`execution-summary` 并生成 `nextActionQueue` 我的下一步队列、继续推进、待确认、review attention、发布关注、发布后反馈关注、下一轮迭代草稿关注、AI 正在推进、草案箱、最近项目摘要;Space P6 的下一步队列按高风险待确认、发布阻塞/失败证据、handoff consumed 后缺少后续 progress、下一轮 draft、发布后反馈/收入/合规信号排序;Space P7 新增 `review_attention`,只读消费 `truth-state.reviewerRouting`,按高风险待我审、`request_changes / rejected` 后待处理、`reviewer_missing` warning、普通 pending review 排序进入“我的下一步”,点击只跳 `/change-review?projectId=<id>&changeSetId=<id>`、已有 `/team-layer?projectId=<id>`、`/execution-bridge?projectId=<id>` 或 `/release-growth?projectId=<id>`;Space P3 的发布关注优先消费 `truth-state.releases` 的 release validation 投影,没有验证投影时才回落 `execution-summary.releaseGate`;Space P4/P5 的发布后反馈/下一轮草稿关注只读消费 `execution-summary.releaseSignals`;不在 Space 写 Project Memory、PMFlowApproval、ProgressEvent、ReleaseValidationRun、FeedbackIngest、RevenueEvent、ComplianceCheckRun、ChangeSet 状态或发布执行,不虚构 reviewer、审查意见或待审 ChangeSet。`components/SpaceChrome.tsx` 承接空间层左侧栏、顶部条与空间用量组件,并被 `/project-launch` 桌面首次新建壳复用;`frontend/src/pages/PersonalSpacePage.tsx` 仅做薄入口。
|
||||
- `frontend/src/features/account-settings/` 是 Account Settings 独立产品域:`AccountSettingsPanel.tsx` 承接 `/me` 桌面设置页、移动端分组列表与 bottom sheet,`frontend/src/api/accountSettings.ts` 定义个人资料、默认语言、AI 回答深度、证据展示偏好和四类通知偏好合同;当前已有账号同步 API fallback、本地 UI 通知消费模型、Release/Growth 偏好 helper、post-release / next change review 偏好 helper,以及 `getAccountReminderConsumption(settings, event)` 提醒强度消费合同,支持 `reminderIntensity=silent|normal|prominent` 与 `evidenceDisplay=compact|expanded`;`releaseGate / failureAlert / pendingConfirmation / executionWriteback` 只影响页面内提醒表达强度和证据展开方式,不代表真实邮件、桌面推送或系统通知中心已接入,也不得隐藏真实业务证据或审计状态,不改 OAuth/JWT/auth core;`frontend/src/features/pmflow-os/pages/AccountPage.tsx` 仅做薄入口并复用既有退出登录 bottom sheet。
|
||||
- `frontend/src/features/pmflow-os/` 当前承载新的对话驱动前端产品域:`store.tsx` 提供共享 truth store;真实模式通过 `?projectId=<id>` 调用 `frontend/src/api/pmflowOs.ts` 读取后端 `ProjectTruthState` projection,并调用真实 message、`proposed_items` extraction、ChangeItem `accept/edit/defer`、ChangeSet `commit`、Execution Handoff create/consume、execution progress writeback、release validation run create、release feedback ingest、单条 iteration recommendation -> ChangeSet draft 与同组 iteration recommendation 批量 -> ChangeSet draft API,成功后 refetch truth-state / execution-summary;显式 `?demo=1` 才启用 `command_idle -> ai_extracting -> extraction_ready -> review_open -> writeback_success` 本地演示状态机,以及本地 `promoteAcceptedToGraph` 等演示动作。`pages/` 下包含 `CommandCenter / StructuredExtraction / ChangeReview / MemoryWritebackResult / WorkGraph / ExecutionBridge / ReleaseGrowth / TeamLayer / ProjectMemory / Account` 主页面;`TeamLayerPage` 真实模式读取 `state.reviewerRouting` 展示 ready/empty/warning、待审 ChangeSet、来源、风险等级、建议审查人或 fallback role、审查原因、待审 item 数和证据数,动作只跳转 `/change-review?projectId=<id>&changeSetId=<id>`,不自动分派 reviewer;`ExecutionBridgePage` 已支持创建 handoff、运行端领取确认和执行进度回写;`ReleaseGrowthPage` 真实模式展示 `execution-summary.releaseGate`、execution evidence、阻塞原因、下一步动作、`latestValidationRun` 和 `releaseSignals`,可创建 release validation run、摄取发布后 feedback、展示 recommendation quality grouping、从真实 recommendation 单条或同组批量生成下一轮待确认 ChangeSet draft,并消费 Account notification preference 静默强调型提醒,但不隐藏真实证据、审计状态或发布后信号;`ChangeReviewPage` 会展示来自 release feedback recommendation group 的组、反馈、风险、影响和审查优先级,帮助 reviewer 判断为什么生成这一组待确认变更;静态发布/增长原型仅保留在显式 demo 模式;后端只记录 progress/release gate/release validation/release feedback/recommendation draft 元数据,不执行本机命令、真实发布命令、外部支付、真实推送、自动 Project Memory commit 或正式任务创建;`ProjectMemoryPage` 在真实模式消费后端 `truth_nodes + semantic_facts` 的 `projectMemory` 投影,真实模式下 accepted/edited ChangeItem 只有 commit 成功后才进入 Project Memory。`components/` 下除了 `ProjectStageRail / ThreadComposer / ExtractionRail / ChangeGroupPanel / ObjectGraphSection / ContextPacketPanel / ExecutionTimeline / ReleaseGatePanel / TeamFeedPanel` 之外,新增 `ProjectStructuredLoop / PMFlowOsFrame / AccountControls / FeedbackPrimitives` 等行为组件,用来承接项目指挥台、结构化提取、待确认审查、项目记忆写入结果、项目内面包屑、项目切换器、账号退出登录交互,以及 `PageBanner / InlineNotice / HumanDecisionBar / ConfirmDialog / MobileBottomSheet` 这组契约化提示提醒组件;`components/MobileBottomNav.tsx` 是正式应用壳唯一移动底部导航,固定为 `空间 / 指挥台 / 待确认 / 动态 / 我的`。
|
||||
- 旧登录实现 `frontend/src/components/ui/sign-in.tsx` 与 `frontend/src/components/ui/demo.tsx` 已移除,`frontend/src/components/login/` 是当前唯一登录真源。
|
||||
|
|
|
|||
|
|
@ -817,6 +817,32 @@ P8 可以三条 lane 并行,但共享高冲突文件必须由 Main Flow 或 Sh
|
|||
|
||||
## 8. 进度台账
|
||||
|
||||
### 2026-04-26 / space-p7-review-attention-queue / implemented
|
||||
|
||||
完成内容:
|
||||
|
||||
- `/space` 的 `nextActionQueue` 新增 `review_attention` 类型,只读消费 `ProjectTruthState.reviewerRouting(status/warningCode/summary/items)`。
|
||||
- Team Review 待审事项按“高风险待我审 -> request_changes/rejected 后待处理 -> reviewer missing warning -> 普通 pending review”排序进入“我的下一步”。
|
||||
- 高风险、普通待审与 review decision 后待处理项只跳 `/change-review?projectId=<id>&changeSetId=<id>`;reviewer 缺失 warning 只跳已有 `/team-layer?projectId=<id>`。
|
||||
- `PersonalSpacePage` 仅补充待审查的队列表达、icon、CTA 与空态文案,不新增模块,不写 ChangeSet、`PMFlowApproval`、ProgressEvent 或 PMFlow OS store。
|
||||
- reviewerRouting 为空时保持空态,不虚构 reviewer、审查意见或待审 ChangeSet。
|
||||
|
||||
涉及文件:
|
||||
|
||||
- `frontend/src/api/space.ts`
|
||||
- `frontend/src/features/personal-space/PersonalSpacePage.tsx`
|
||||
- `frontend/src/features/personal-space/PersonalSpacePage.test.tsx`
|
||||
- `CLAUDE.md`
|
||||
- `docs/PMFLOW_PRODUCT_DELIVERY_STANDARD.md`
|
||||
- `docs/architecture/CODE_REFERENCE_INDEX.md`
|
||||
- `docs/architecture/ARCHITECTURE_INVENTORY.md`
|
||||
|
||||
验证:
|
||||
|
||||
- `cd frontend && npm run test -- src/features/personal-space/PersonalSpacePage.test.tsx` 通过:`3 passed`;仍有既有 React Router v7 future flag warning。
|
||||
- `cd frontend && npm run typecheck` 通过。
|
||||
- `cd frontend && npm run build` 通过;Vite 仍提示既有 manual chunk circular warning(`ai-steward / sstc / charts / governance` chunk 环),构建成功。
|
||||
|
||||
### 2026-04-26 / shell-p8-rebaseline-next-plan / done
|
||||
|
||||
完成内容:
|
||||
|
|
|
|||
|
|
@ -195,9 +195,9 @@
|
|||
- 登录页与营销页仍共享 `frontend/src/components/marketing/` 的部分视觉基线;公开登录页现改由 `frontend/src/components/login/` 目录承载,其中 `LoginExperience.tsx` 负责登录壳层、`LoginWorkflowVisual.tsx` 负责左侧场景、`loginScene.ts` / `loginMath.ts` 拆出展示配置与路径几何、`login.css` 负责登录页专属样式边界,`frontend/src/pages/login/LoginPage.tsx` 继续负责真实鉴权、错误态和 redirect 提示;未登录访问 `/` 会经 `frontend/src/router/index.tsx` 直接重定向到 `/login`,无显式 `redirect` 的成功登录现在默认进入 `/command-center`。
|
||||
- 前端主包治理已切到“同步首屏最小集 + route/page-section lazy”模式:`frontend/src/router/index.tsx` 保留 `LoginPage / Layout` 这组同步入口,其余后台重页面通过 `React.lazy + Suspense` 懒加载;统一 fallback 在 `frontend/src/components/layout/LazyBoundary.tsx`。P0 公共壳冻结后,大模块 route 归属为:`/space` 归 Space lane,`/project-launch` 是启动层入口,`/pmflow-os` 与 `/command-center / /change-review / /execution-bridge / /release-growth` 归 Main Flow lane,`/me` 归 Account Settings lane,`/settings` 当前仅为 reserved route、尚未实现,新增必须由 Shell / Integration owner 串行处理。`frontend/src/features/personal-space/` 作为空间层入口,通过 `/space` 提供可直接打开的本地预览入口,承载继续推进、待我确认、AI 正在推进、暂存想法、草案箱与新建项目入口。`frontend/src/features/project-launch/` 作为首次进入页 / 新项目启动台产品域,通过 `/project-launch` 提供可直接打开的入口;Phase B 后默认调用真实 `pmflow-launch` draft/confirm API,创建成功态主动作跳转后端返回的 `/pmflow-os?projectId=<id>`,只有显式 `?demo=1` 或 `?dataSource=demo` 才启用本地 mock 状态机。`frontend/src/features/account-settings/` 作为 Account Settings 产品域,通过 `/me` 提供个人资料、默认语言、AI 回答深度、证据展示偏好、待确认/执行回写/发布门禁/失败提醒通知偏好和账户操作入口;该域通过 `frontend/src/api/accountSettings.ts` 建立前端合同,有 token 时优先调用已挂入 `backend/app/api/router.py` 的 `backend/app/api/account_settings_api/` `GET /account-settings/me` / `PUT /account-settings/me` 同步当前账号偏好,后端不可用时明确回落本机暂存,不修改 OAuth/JWT/auth core。`frontend/src/features/pmflow-os/` 作为新产品域,通过 `PMFlowOsProvider` 在应用根部挂载共享 truth store,并提供 `CommandCenter / ChangeReview / WorkGraph / ExecutionBridge / ReleaseGrowth / TeamLayer / ProjectMemory / Account` 主入口;当前 PMFlow OS 页面已切到专属轻壳,桌面端默认隐藏旧 TopBar,并由 `frontend/src/features/pmflow-os/components/PMFlowOsFrame.tsx` 统一承接“左侧稳定导航 + 顶部上下文条 + 中央视图 + 右侧 AI 副驾”的全局骨架,同时承接项目内 `个人空间 / 当前项目名` 面包屑和项目切换器;账号退出登录由 `frontend/src/features/pmflow-os/components/AccountControls.tsx` 统一承接桌面头像下拉、退出确认与移动端 bottom sheet,`frontend/src/features/pmflow-os/pages/AccountPage.tsx` 提供 `/me` 入口并委托 Account Settings 域渲染设置页;正式应用壳移动端底部导航统一由 `frontend/src/features/pmflow-os/components/MobileBottomNav.tsx` 承接,固定为 `空间 / 指挥台 / 待确认 / 动态 / 我的`,其中 `动态` 当前仍是 `/command-center` 占位。`/command-center` 额外提供免登录产品预览壳,`/pmflow-os` 是真实项目上下文 alias。主视觉按 `#2463EB + 白色` 的 Linear-like 高密度原型展开。`UsersPage` 把创建/删除/重置/导入/批量分配对话框拆为按需 chunk,`UserSkillsPage` 与 `WorkloadDetailPage` 的图表也分别拆到了 `frontend/src/pages/user-skills/components/UserSkillsRadarPanel.tsx` 与 `frontend/src/pages/workload-detail/components/WorkloadTrendChart.tsx`。
|
||||
- PMFlow P8 当前推进真源已从已完成的 Team Review P1 重定为 Review Decision Loop:下一阶段基于 `ProjectTruthState.reviewerRouting` 只读模型推进 `review request -> reviewer decision -> approval audit -> personal attention`,建议并行边界为 `main-flow-p8-review-decision-loop`、`space-p7-review-attention-queue`、`account-p8-review-decision-preference`,最终由 `big-module-integration-p8` 收口;P8 不新增战略页面、不改 router/nav、不接真实通知中心,继续使用既有 `/change-review / /team-layer / /space / /me` 表面。
|
||||
- `frontend/src/features/personal-space/PersonalSpacePage.tsx` 是空间首页视图层,`frontend/src/api/space.ts` 是空间工作面只读聚合 API:从 `projects` summary、PMFlow OS `truth-state` 与 `execution-summary` 生成 `nextActionQueue` 我的下一步队列、继续推进、待确认、发布关注、发布后反馈关注、下一轮迭代草稿关注、执行中 / AI 正在推进、暂存想法、草案箱、最近项目摘要;Space P6 的 `nextActionQueue` 按高风险 ChangeSet 待确认、release blocked / failed evidence、handoff consumed 但无后续 progress、iteration draft 待确认、feedback/revenue/compliance 信号待处理排序,并只提供 `/change-review`、`/execution-bridge`、`/release-growth` 跳转。Space P3 的 `releaseAttention` 优先消费 `truth-state.releases` 中由 release validation run 投影出的 release 状态与检查项,把“发布验证阻塞 / 发布验证待确认”显示到 `/space`,没有发布验证投影时才回落到 `execution-summary.releaseGate` 的发布候选或发布门禁阻塞;Space P4/P5 的 `feedbackAttention` 只读消费 `execution-summary.releaseSignals`,把发布后 feedback、revenue、ROI、compliance、iteration recommendation 与已生成的下一轮 ChangeSet draft 关注项显示到 `/space`。发布验证、发布后信号或下一轮 draft 缺失时显示明确空态,不虚构验证、反馈、收入、ROI、合规、建议或 ChangeSet draft 结果。项目主动作进入 `/pmflow-os?projectId=<id>`,待确认项进入 `/change-review?projectId=<id>&changeSetId=<id>`,发布关注和发布后反馈关注项进入 `/release-growth?projectId=<id>`,下一轮 draft 关注项进入 `/change-review?projectId=<id>&changeSetId=<id>`,执行中项进入 `/execution-bridge?projectId=<id>`;暂存想法后端只读聚合尚未接入时只显示 empty/warning,不使用 mock 数据冒充真实内容;移动端为空间首页独立 App 布局,不直接压缩桌面卡片,并保留最近项目区块;`frontend/src/pages/PersonalSpacePage.tsx` 是薄入口。该域只表达个人空间和主流程导航,不做团队空间强切换、企业组织管理、Project Memory 写入、ChangeSet 状态写入、ProgressEvent 写入、ReleaseValidationRun 写入、FeedbackIngest 写入或发布执行。
|
||||
- `frontend/src/features/personal-space/PersonalSpacePage.tsx` 是空间首页视图层,`frontend/src/api/space.ts` 是空间工作面只读聚合 API:从 `projects` summary、PMFlow OS `truth-state` 与 `execution-summary` 生成 `nextActionQueue` 我的下一步队列、继续推进、待确认、发布关注、发布后反馈关注、下一轮迭代草稿关注、执行中 / AI 正在推进、暂存想法、草案箱、最近项目摘要;Space P6 的 `nextActionQueue` 按高风险 ChangeSet 待确认、release blocked / failed evidence、handoff consumed 但无后续 progress、iteration draft 待确认、feedback/revenue/compliance 信号待处理排序,并只提供 `/change-review`、`/execution-bridge`、`/release-growth` 跳转;Space P7 新增 `review_attention`,只读消费 `truth-state.reviewerRouting`,把高风险待我审、`request_changes / rejected` 后待处理、`reviewer_missing` warning 与普通 pending review 排入“我的下一步”,待审/退回/拒绝项只跳 `/change-review?projectId=<id>&changeSetId=<id>`,reviewer 缺失 warning 只跳已有 `/team-layer?projectId=<id>`。Space P3 的 `releaseAttention` 优先消费 `truth-state.releases` 中由 release validation run 投影出的 release 状态与检查项,把“发布验证阻塞 / 发布验证待确认”显示到 `/space`,没有发布验证投影时才回落到 `execution-summary.releaseGate` 的发布候选或发布门禁阻塞;Space P4/P5 的 `feedbackAttention` 只读消费 `execution-summary.releaseSignals`,把发布后 feedback、revenue、ROI、compliance、iteration recommendation 与已生成的下一轮 ChangeSet draft 关注项显示到 `/space`。reviewerRouting、发布验证、发布后信号或下一轮 draft 缺失时显示明确空态,不虚构 reviewer、审查意见、待审 ChangeSet、验证、反馈、收入、ROI、合规、建议或 ChangeSet draft 结果。项目主动作进入 `/pmflow-os?projectId=<id>`,待确认项进入 `/change-review?projectId=<id>&changeSetId=<id>`,发布关注和发布后反馈关注项进入 `/release-growth?projectId=<id>`,下一轮 draft 关注项进入 `/change-review?projectId=<id>&changeSetId=<id>`,执行中项进入 `/execution-bridge?projectId=<id>`;暂存想法后端只读聚合尚未接入时只显示 empty/warning,不使用 mock 数据冒充真实内容;移动端为空间首页独立 App 布局,不直接压缩桌面卡片,并保留最近项目区块;`frontend/src/pages/PersonalSpacePage.tsx` 是薄入口。该域只表达个人空间和主流程导航,不做团队空间强切换、企业组织管理、Project Memory 写入、ChangeSet 状态写入、PMFlowApproval 写入、ProgressEvent 写入、ReleaseValidationRun 写入、FeedbackIngest 写入或发布执行。
|
||||
- 前端主包治理已切到“同步首屏最小集 + route/page-section lazy”模式:`frontend/src/router/index.tsx` 保留 `LoginPage / Layout` 这组同步入口,其余后台重页面通过 `React.lazy + Suspense` 懒加载;统一 fallback 在 `frontend/src/components/layout/LazyBoundary.tsx`。P0 公共壳冻结后,大模块 route 归属为:`/space` 归 Space lane,`/project-launch` 是启动层入口,`/pmflow-os` 与 `/command-center / /change-review / /execution-bridge / /release-growth` 归 Main Flow lane,`/me` 归 Account Settings lane,`/settings` 当前仅为 reserved route、尚未实现,新增必须由 Shell / Integration owner 串行处理。`frontend/src/features/personal-space/` 作为空间层入口,通过 `/space` 提供可直接打开的本地预览入口,承载继续推进、待我确认、AI 正在推进、暂存想法、草案箱与新建项目入口。`frontend/src/features/project-launch/` 作为首次进入页 / 新项目启动台产品域,通过 `/project-launch` 提供可直接打开的入口;Phase B 后默认调用真实 `pmflow-launch` draft/confirm API,创建成功态主动作跳转后端返回的 `/pmflow-os?projectId=<id>`,只有显式 `?demo=1` 或 `?dataSource=demo` 才启用本地 mock 状态机。`frontend/src/features/account-settings/` 作为 Account Settings 产品域,通过 `/me` 提供个人资料、默认语言、AI 回答深度、证据展示偏好、待确认/执行回写/发布门禁/失败提醒通知偏好和账户操作入口;该域通过 `frontend/src/api/accountSettings.ts` 建立前端合同,有 token 时优先调用已挂入 `backend/app/api/router.py` 的 `backend/app/api/account_settings_api/` `GET /account-settings/me` / `PUT /account-settings/me` 同步当前账号偏好,后端不可用时明确回落本机暂存,不修改 OAuth/JWT/auth core。`frontend/src/features/pmflow-os/` 作为新产品域,通过 `PMFlowOsProvider` 在应用根部挂载共享 truth store,并提供 `CommandCenter / ChangeReview / WorkGraph / ExecutionBridge / ReleaseGrowth / TeamLayer / ProjectMemory / Account` 主入口;当前 PMFlow OS 页面已切到专属轻壳,桌面端默认隐藏旧 TopBar,并由 `frontend/src/features/pmflow-os/components/PMFlowOsFrame.tsx` 统一承接“左侧稳定导航 + 顶部上下文条 + 中央视图 + 右侧 AI 副驾”的全局骨架,同时承接项目内 `个人空间 / 当前项目名` 面包屑和项目切换器;`TeamLayerPage.tsx` 现在消费 `ProjectTruthState.reviewerRouting(status/summary/items)` 展示待审 ChangeSet、来源、风险等级、建议审查人或 fallback role、审查原因、待审 item 数、证据数和 warning,动作只跳 `/change-review?projectId=<id>&changeSetId=<id>`,不自动 assign reviewer;账号退出登录由 `frontend/src/features/pmflow-os/components/AccountControls.tsx` 统一承接桌面头像下拉、退出确认与移动端 bottom sheet,`frontend/src/features/pmflow-os/pages/AccountPage.tsx` 提供 `/me` 入口并委托 Account Settings 域渲染设置页;正式应用壳移动端底部导航统一由 `frontend/src/features/pmflow-os/components/MobileBottomNav.tsx` 承接,固定为 `空间 / 指挥台 / 待确认 / 动态 / 我的`,其中 `动态` 当前仍是 `/command-center` 占位。`/command-center` 额外提供免登录产品预览壳,`/pmflow-os` 是真实项目上下文 alias。主视觉按 `#2463EB + 白色` 的 Linear-like 高密度原型展开。`UsersPage` 把创建/删除/重置/导入/批量分配对话框拆为按需 chunk,`UserSkillsPage` 与 `WorkloadDetailPage` 的图表也分别拆到了 `frontend/src/pages/user-skills/components/UserSkillsRadarPanel.tsx` 与 `frontend/src/pages/workload-detail/components/WorkloadTrendChart.tsx`。
|
||||
- `frontend/src/features/personal-space/PersonalSpacePage.tsx` 是空间首页视图层,`frontend/src/api/space.ts` 是空间工作面只读聚合 API:从 `projects` summary、PMFlow OS `truth-state` 与 `execution-summary` 生成继续推进、待确认、发布关注、发布后反馈关注、下一轮迭代草稿关注、执行中 / AI 正在推进、暂存想法、草案箱、最近项目摘要;Space P3 的 `releaseAttention` 优先消费 `truth-state.releases` 中由 release validation run 投影出的 release 状态与检查项,把“发布验证阻塞 / 发布验证待确认”显示到 `/space`,没有发布验证投影时才回落到 `execution-summary.releaseGate` 的发布候选或发布门禁阻塞;Space P4/P5 的 `feedbackAttention` 只读消费 `execution-summary.releaseSignals`,把发布后 feedback、revenue、ROI、compliance、iteration recommendation 与已生成的下一轮 ChangeSet draft 关注项显示到 `/space`。发布验证、发布后信号或下一轮 draft 缺失时显示明确空态,不虚构验证、反馈、收入、ROI、合规、建议或 ChangeSet draft 结果。项目主动作进入 `/pmflow-os?projectId=<id>`,待确认项进入 `/change-review?projectId=<id>&changeSetId=<id>`,发布关注和发布后反馈关注项进入 `/release-growth?projectId=<id>`,下一轮 draft 关注项进入 `/change-review?projectId=<id>&changeSetId=<id>`,执行中项进入 `/execution-bridge?projectId=<id>`;暂存想法后端只读聚合尚未接入时只显示 empty/warning,不使用 mock 数据冒充真实内容;移动端为空间首页独立 App 布局,不直接压缩桌面卡片,并保留最近项目区块;`frontend/src/pages/PersonalSpacePage.tsx` 是薄入口。该域只表达个人空间和主流程导航,不做团队空间强切换、企业组织管理、Project Memory 写入、ChangeSet 状态写入、ProgressEvent 写入、ReleaseValidationRun 写入、FeedbackIngest 写入或发布执行。
|
||||
- `frontend/src/features/personal-space/PersonalSpacePage.tsx` 是空间首页视图层,`frontend/src/api/space.ts` 是空间工作面只读聚合 API:从 `projects` summary、PMFlow OS `truth-state` 与 `execution-summary` 生成继续推进、待确认、review attention、发布关注、发布后反馈关注、下一轮迭代草稿关注、执行中 / AI 正在推进、暂存想法、草案箱、最近项目摘要;`review_attention` 只读消费 `truth-state.reviewerRouting`,用于把高风险待我审、审查退回/拒绝、reviewer 缺失 warning 与普通 pending review 加入“我的下一步”。发布验证、发布后信号、reviewerRouting 或下一轮 draft 缺失时显示明确空态,不虚构 reviewer、审查意见、待审 ChangeSet、验证、反馈、收入、ROI、合规、建议或 ChangeSet draft 结果。项目主动作进入 `/pmflow-os?projectId=<id>`,待确认和待审项进入 `/change-review?projectId=<id>&changeSetId=<id>`,reviewer 缺失 warning 进入已有 `/team-layer?projectId=<id>`,发布关注和发布后反馈关注项进入 `/release-growth?projectId=<id>`,下一轮 draft 关注项进入 `/change-review?projectId=<id>&changeSetId=<id>`,执行中项进入 `/execution-bridge?projectId=<id>`;暂存想法后端只读聚合尚未接入时只显示 empty/warning,不使用 mock 数据冒充真实内容;移动端为空间首页独立 App 布局,不直接压缩桌面卡片,并保留最近项目区块;`frontend/src/pages/PersonalSpacePage.tsx` 是薄入口。该域只表达个人空间和主流程导航,不做团队空间强切换、企业组织管理、Project Memory 写入、ChangeSet 状态写入、PMFlowApproval 写入、ProgressEvent 写入、ReleaseValidationRun 写入、FeedbackIngest 写入或发布执行。
|
||||
- `frontend/src/features/project-launch/types.ts`、`mockData.ts`、`useProjectLaunchState.ts` 是新项目启动台状态层;默认真实模式通过 `frontend/src/api/pmflowLaunch.ts` 生成草案并确认创建真实项目,API 失败进入明确失败态,不静默 fallback 成 mock 成功;`mockData.ts` 仅在显式 `?demo=1` 或 `?dataSource=demo` 下作为演示状态机使用。`components/LaunchShell.tsx` 负责桌面/移动分流,桌面端复用 `frontend/src/features/personal-space/components/SpaceChrome.tsx` 的空间左侧栏和顶部条,`LaunchCanvases.tsx` 和 `MobileLaunchLayout.tsx` 承接高保真图中的意图输入、AI 工作、人类确认、草案修改、生成失败、创建成功过渡与移动端 bottom sheet 交互。
|
||||
- `frontend/src/features/account-settings/AccountSettingsPanel.tsx` 是 `/me` 设置页实现:桌面端为个人资料摘要 + 分区设置面板,移动端为分组列表 + `MobileBottomSheet` 编辑;当前覆盖个人资料、默认语言、AI 回答深度、证据展示偏好、待确认、执行回写、发布门禁和失败提醒通知偏好,并在通知偏好区展示页面内提醒消费说明。`frontend/src/api/accountSettings.ts` 是前端合同层;有 token 时优先调用 `backend/app/api/account_settings_api/`,失败时回落浏览器本机暂存;P2 新增 `shouldShowAccountNotification` 与 `getAccountNotificationConsumption`,将四类通知偏好映射为本地 UI 的待确认、Execution Bridge、Release/Growth 和失败提醒显示/静默状态;P3 新增 `getReleaseGateNotificationPreference(settings, event)`,把 Release/Growth 的 `release_gate_* / release_validation_*` 事件映射到 `releaseGate` 偏好,把 `failed_event` 映射到 `failureAlert` 偏好;P5/P6 的 `getPostReleaseNotificationPreference(settings, event)` 把发布后 `feedback_received / revenue_signal / compliance_blocked` 映射到 `releaseGate` 偏好,把 `iteration_recommendation / next_change_review` 映射到 `pendingConfirmation` 偏好和 `change_review` surface,并返回 `spaceReadable / evidencePolicy / reminderPolicy` 供 Main Flow 与 Space 消费。该合同只控制页面内提醒表达,不代表邮件、桌面推送或系统通知中心已接入;偏好关闭也不得隐藏业务证据、recommendation 或 ChangeSet。`backend/app/schemas/account_settings.py` 定义账号偏好合同,`backend/app/services/account_settings_service.py` 当前复用既有 `integrations` 表按 `tenant_id + user_id` 保存 `account_settings_preferences` 配置包,`backend/app/api/router.py` 已挂载该 API;未改用户表、OAuth/JWT/auth core 或 PMFlow OS truth store。
|
||||
- `frontend/src/features/pmflow-os/store.tsx` 当前是 PMFlow OS 共享 truth store:`?projectId=<id>` 时通过 React Query 调用 `frontend/src/api/pmflowOs.ts` 读取真实 `GET /pmflow-os/projects/{project_id}/truth-state` 与 `GET /pmflow-os/projects/{project_id}/execution-summary`,真实模式下项目内 message、`proposed_items` extraction、ChangeItem `accept/edit/defer`、ChangeSet commit、Execution Handoff create/consume、execution progress writeback、release validation run create、release feedback ingest、单条 iteration recommendation -> ChangeSet draft 与同组 iteration recommendation 批量 -> ChangeSet draft 均调用 `/pmflow-os` 后端并 refetch;只有显式 `?demo=1` 或 `?dataSource=demo` 时才启用本地 mock 演示状态机。真实读取/动作失败会进入错误/空态,不静默 fallback 成 mock 成功。
|
||||
|
|
|
|||
|
|
@ -112,6 +112,7 @@
|
|||
- PMFlow OS 产品页入口:参考 `frontend/src/features/pmflow-os/pages/CommandCenterPage.tsx`、`StructuredExtractionPage.tsx`、`ChangeReviewPage.tsx`、`MemoryWritebackResultPage.tsx`、`WorkGraphPage.tsx`、`ExecutionBridgePage.tsx`、`ReleaseGrowthPage.tsx`、`TeamLayerPage.tsx`、`ProjectMemoryPage.tsx`、`AccountPage.tsx`;`TeamLayerPage.tsx` 真实模式读取 `state.reviewerRouting` 展示 ready/empty/warning、待审 ChangeSet、来源(下一轮迭代 / AI 提取 / 手动)、风险等级、建议审查人或 fallback role、审查原因、待审 item 数、证据数,并只跳转 `/change-review?projectId=<id>&changeSetId=<id>`,不自动分派 reviewer;`ReleaseGrowthPage.tsx` 真实模式展示 `execution-summary.releaseGate`、execution evidence、阻塞原因、下一步动作、`latestValidationRun` 和 `releaseSignals`,缺证据或缺发布后信号时显示 empty/warning,不展示 mock 发布结果;页面可调用真实 API 创建 release validation run、摄取 release feedback,从真实 recommendation 单条或同组批量生成下一轮待确认 ChangeSet draft,并解释 `iteration_recommendation_group` evidence 的组、反馈、风险、影响和审查优先级;生成后仍进入 Change Review accept/edit/defer,不自动 commit、不写 Project Memory、不执行部署命令、不接外部支付;同时消费 Account notification preference helper,`releaseGate` 关闭时静默发布门禁强调提醒,`failureAlert` 关闭时静默失败强调提醒,所有真实证据和审计状态仍可见;静态高保真发布/增长数据仅保留在显式 demo 模式
|
||||
- PMFlow OS 行为组件:参考 `frontend/src/features/pmflow-os/components/ProjectStructuredLoop.tsx`、`ProjectStageRail.tsx`、`ThreadComposer.tsx`、`ExtractionRail.tsx`、`ChangeGroupPanel.tsx`、`ObjectGraphSection.tsx`、`ContextPacketPanel.tsx`、`ExecutionTimeline.tsx`、`ReleaseGatePanel.tsx`、`TeamFeedPanel.tsx`、`ChangeReviewGroup.tsx`、`WorkbenchPanel.tsx`、`ExecutionBridgeCards.tsx`、`PMFlowOsFrame.tsx`、`AccountControls.tsx`、`FeedbackPrimitives.tsx`、`MobileBottomNav.tsx`;其中 `ProjectStructuredLoop.tsx` 承接 `StatusLabel / EvidenceSnippet / ObjectChangeCard / ProjectConversationPanel / StructuredExtractionPanel / ProjectAICopilotPanel`,`PMFlowOsFrame.tsx` 承接项目内面包屑和项目切换器,`AccountControls.tsx` 承接桌面头像菜单、退出登录确认与移动端 bottom sheet,`FeedbackPrimitives.tsx` 承接 `PageBanner / InlineNotice / HumanDecisionBar / ConfirmDialog / MobileBottomSheet`,`MobileBottomNav.tsx` 承接正式应用壳移动端固定底部导航
|
||||
- PMFlow OS 原型状态回归测试:参考 `frontend/src/features/pmflow-os/store.test.ts`;真实 API 动作回归参考 `frontend/src/features/pmflow-os/store.api.test.tsx`
|
||||
- Space 工作面回归测试:参考 `frontend/src/features/personal-space/PersonalSpacePage.test.tsx`,覆盖空态、导航只读约束、发布/反馈/迭代草案队列,以及 reviewerRouting -> `review_attention` 的 next action 聚合排序与 `/change-review`、`/team-layer` 跳转合同。
|
||||
- Project Launch 真实/演示分支回归测试:参考 `frontend/src/features/project-launch/useProjectLaunchState.test.ts`
|
||||
- Account Settings 前端合同回归测试:参考 `frontend/src/features/account-settings/accountSettings.test.ts`,覆盖默认偏好、账号同步 fallback、通知偏好消费模型、提醒强度/证据展示合同、Release/Growth 通知消费合同、失败事件映射和缺字段 fallback。
|
||||
- 网页问答显式锚点与 AI Steward URL 构造:参考 `frontend/src/components/ai-steward/qaPageAnchor.ts`
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import type {
|
|||
MetricSignal,
|
||||
PMFlowExecutionStatus,
|
||||
PMFlowObjectStatus,
|
||||
PMFlowReviewerRoutingItem,
|
||||
Release,
|
||||
ProjectTruthState,
|
||||
} from "@/features/pmflow-os/types";
|
||||
|
|
@ -95,6 +96,7 @@ export interface SpaceIterationDraftAttentionItem {
|
|||
|
||||
export type SpaceNextActionKind =
|
||||
| "pending_confirmation"
|
||||
| "review_attention"
|
||||
| "release_blocked"
|
||||
| "execution_attention"
|
||||
| "iteration_draft"
|
||||
|
|
@ -245,6 +247,10 @@ function releaseGrowthHref(projectId: string): string {
|
|||
return `/release-growth?projectId=${encodeURIComponent(projectId)}`;
|
||||
}
|
||||
|
||||
function teamLayerHref(projectId: string): string {
|
||||
return `/team-layer?projectId=${encodeURIComponent(projectId)}`;
|
||||
}
|
||||
|
||||
function pendingChangeItems(truth: ProjectTruthState | null): ChangeItem[] {
|
||||
if (!truth) return [];
|
||||
return truth.changeItems.filter((item) => item.status === "pending_review");
|
||||
|
|
@ -728,6 +734,134 @@ function nextActionPriorityLabel(severity: "low" | "medium" | "high"): string {
|
|||
return "低优先级";
|
||||
}
|
||||
|
||||
function reviewRoutingHref(projectId: string, item: PMFlowReviewerRoutingItem, preferTeamLayer = false): string {
|
||||
const targetUrl = String(item.targetUrl || "");
|
||||
if (targetUrl.startsWith("/change-review") || targetUrl.startsWith("/team-layer")) {
|
||||
return targetUrl;
|
||||
}
|
||||
if (preferTeamLayer) return teamLayerHref(projectId);
|
||||
return reviewHref(projectId, item.changeSetId);
|
||||
}
|
||||
|
||||
function reviewDecisionState(item: PMFlowReviewerRoutingItem): "request_changes" | "rejected" | null {
|
||||
const record = item as unknown as Record<string, unknown>;
|
||||
const raw = String(
|
||||
record.reviewDecision ||
|
||||
record.review_decision ||
|
||||
record.approvalState ||
|
||||
record.approval_state ||
|
||||
record.approvalStatus ||
|
||||
record.approval_status ||
|
||||
""
|
||||
).toLowerCase();
|
||||
if (raw === "request_changes" || raw === "changes_requested" || raw === "needs_changes") return "request_changes";
|
||||
if (raw === "rejected" || raw === "reject") return "rejected";
|
||||
return null;
|
||||
}
|
||||
|
||||
function isReviewerMissing(item: PMFlowReviewerRoutingItem): boolean {
|
||||
return !item.suggestedReviewer && !item.fallbackReviewerRole;
|
||||
}
|
||||
|
||||
function reviewAttentionPriorityRank(item: PMFlowReviewerRoutingItem): number {
|
||||
if (item.riskLevel === "high" && item.priority === "urgent") return 8;
|
||||
if (item.riskLevel === "high") return 9;
|
||||
if (reviewDecisionState(item)) return 14;
|
||||
if (isReviewerMissing(item)) return 24;
|
||||
return 32;
|
||||
}
|
||||
|
||||
function reviewAttentionSeverity(item: PMFlowReviewerRoutingItem): "low" | "medium" | "high" {
|
||||
if (item.riskLevel === "high" || item.priority === "urgent" || reviewDecisionState(item) === "rejected") return "high";
|
||||
if (item.riskLevel === "medium" || item.priority === "elevated" || reviewDecisionState(item)) return "medium";
|
||||
return "low";
|
||||
}
|
||||
|
||||
function reviewAttentionTitle(item: PMFlowReviewerRoutingItem): string {
|
||||
const decisionState = reviewDecisionState(item);
|
||||
if (decisionState === "request_changes") return "审查退回待处理";
|
||||
if (decisionState === "rejected") return "审查拒绝待处理";
|
||||
if (isReviewerMissing(item)) return "审查人缺失";
|
||||
if (item.riskLevel === "high") return "高风险 ChangeSet 待我审";
|
||||
return "ChangeSet 待我审";
|
||||
}
|
||||
|
||||
function reviewAttentionCategoryLabel(item: PMFlowReviewerRoutingItem): string {
|
||||
const decisionState = reviewDecisionState(item);
|
||||
if (decisionState) return decisionState === "request_changes" ? "审查退回" : "审查拒绝";
|
||||
if (isReviewerMissing(item)) return "审查人缺失";
|
||||
if (item.riskLevel === "high") return "高风险待审";
|
||||
return "待审查";
|
||||
}
|
||||
|
||||
function reviewAttentionSummary(item: PMFlowReviewerRoutingItem): string {
|
||||
const reviewer =
|
||||
item.suggestedReviewer?.displayName ||
|
||||
item.suggestedReviewer?.role ||
|
||||
(item.fallbackReviewerRole ? `${item.fallbackReviewerRole} 角色` : "");
|
||||
const decisionState = reviewDecisionState(item);
|
||||
if (decisionState === "request_changes") {
|
||||
return `${item.changeSetTitle} 已被要求修改,需要回到 Change Review 处理审查意见。`;
|
||||
}
|
||||
if (decisionState === "rejected") {
|
||||
return `${item.changeSetTitle} 已被拒绝,需要回到 Change Review 判断重做、延期或关闭。`;
|
||||
}
|
||||
if (isReviewerMissing(item)) {
|
||||
return `${item.changeSetTitle} 缺少可用审查人,项目需要先补齐 reviewer routing 后再推进。`;
|
||||
}
|
||||
return reviewer
|
||||
? `${item.changeSetTitle} 需要 ${reviewer} 审查;${item.reviewReason}`
|
||||
: `${item.changeSetTitle} 需要审查;${item.reviewReason}`;
|
||||
}
|
||||
|
||||
function deriveReviewAttentionActions(snapshot: ProjectSnapshot): SpaceNextActionQueueItem[] {
|
||||
const { project, truth } = snapshot;
|
||||
const routing = truth?.reviewerRouting;
|
||||
if (!routing) return [];
|
||||
|
||||
const projectId = String(project.id);
|
||||
const projectTitle = project.title || truth.project.name || `项目 ${projectId}`;
|
||||
const routedItems = routing.items.map((item): SpaceNextActionQueueItem => {
|
||||
const severity = reviewAttentionSeverity(item);
|
||||
const missingReviewer = isReviewerMissing(item) && !reviewDecisionState(item);
|
||||
return {
|
||||
id: `next-action-review-${projectId}-${item.changeSetId}`,
|
||||
projectId,
|
||||
projectTitle,
|
||||
kind: "review_attention",
|
||||
title: reviewAttentionTitle(item),
|
||||
summary: reviewAttentionSummary(item),
|
||||
categoryLabel: reviewAttentionCategoryLabel(item),
|
||||
priorityLabel: nextActionPriorityLabel(severity),
|
||||
priorityRank: reviewAttentionPriorityRank(item),
|
||||
severity,
|
||||
updatedAt: item.updatedAt || item.createdAt || project.updated_at,
|
||||
href: reviewRoutingHref(projectId, item, missingReviewer),
|
||||
};
|
||||
});
|
||||
|
||||
if (routedItems.length || routing.status !== "warning" || routing.summary.missingReviewerCount <= 0) {
|
||||
return routedItems;
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
id: `next-action-review-warning-${projectId}`,
|
||||
projectId,
|
||||
projectTitle,
|
||||
kind: "review_attention",
|
||||
title: "审查路由待处理",
|
||||
summary: `${routing.summary.missingReviewerCount} 个待审 ChangeSet 缺少 reviewer routing,需要回到团队层查看。`,
|
||||
categoryLabel: "审查人缺失",
|
||||
priorityLabel: "中优先级",
|
||||
priorityRank: 24,
|
||||
severity: "medium",
|
||||
updatedAt: project.updated_at,
|
||||
href: teamLayerHref(projectId),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function deriveStaleExecutionActions(snapshot: ProjectSnapshot): SpaceNextActionQueueItem[] {
|
||||
const { project, truth, execution } = snapshot;
|
||||
const events = execution?.recentProgressEvents ?? [];
|
||||
|
|
@ -774,6 +908,7 @@ function buildNextActionQueue({
|
|||
releaseAttention,
|
||||
feedbackAttention,
|
||||
iterationDraftAttention,
|
||||
reviewAttentionActions,
|
||||
staleExecutionActions,
|
||||
}: {
|
||||
pendingConfirmations: SpacePendingConfirmation[];
|
||||
|
|
@ -781,6 +916,7 @@ function buildNextActionQueue({
|
|||
releaseAttention: SpaceReleaseAttentionItem[];
|
||||
feedbackAttention: SpaceFeedbackAttentionItem[];
|
||||
iterationDraftAttention: SpaceIterationDraftAttentionItem[];
|
||||
reviewAttentionActions: SpaceNextActionQueueItem[];
|
||||
staleExecutionActions: SpaceNextActionQueueItem[];
|
||||
}): SpaceNextActionQueueItem[] {
|
||||
const pendingActions = pendingConfirmations.map((item): SpaceNextActionQueueItem => {
|
||||
|
|
@ -864,7 +1000,7 @@ function buildNextActionQueue({
|
|||
}));
|
||||
|
||||
const seen = new Set<string>();
|
||||
return [...pendingActions, ...releaseBlockedActions, ...staleExecutionActions, ...activeExecutionActions, ...iterationActions, ...feedbackActions]
|
||||
return [...reviewAttentionActions, ...pendingActions, ...releaseBlockedActions, ...staleExecutionActions, ...activeExecutionActions, ...iterationActions, ...feedbackActions]
|
||||
.filter((item) => {
|
||||
const key = `${item.kind}:${item.href}:${item.title}`;
|
||||
if (seen.has(key)) return false;
|
||||
|
|
@ -905,6 +1041,7 @@ export const spaceApi = {
|
|||
const releaseAttention = sortByDate(snapshots.flatMap(deriveReleaseAttention)).slice(0, 8);
|
||||
const feedbackAttention = sortByDate(snapshots.flatMap(deriveFeedbackAttention)).slice(0, 8);
|
||||
const iterationDraftAttention = sortByDate(snapshots.flatMap(deriveIterationDraftAttention)).slice(0, 8);
|
||||
const reviewAttentionActions = snapshots.flatMap(deriveReviewAttentionActions);
|
||||
const staleExecutionActions = sortByDate(snapshots.flatMap(deriveStaleExecutionActions)).slice(0, 4);
|
||||
return {
|
||||
projects: workbenchProjects.slice(0, 4),
|
||||
|
|
@ -914,6 +1051,7 @@ export const spaceApi = {
|
|||
releaseAttention,
|
||||
feedbackAttention,
|
||||
iterationDraftAttention,
|
||||
reviewAttentionActions,
|
||||
staleExecutionActions,
|
||||
}),
|
||||
pendingConfirmations,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import { screen } from "@testing-library/react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import PersonalSpacePage from "./PersonalSpacePage";
|
||||
import { getPMFlowExecutionSummary, getPMFlowTruthState } from "@/api/pmflowOs";
|
||||
import { projectsApi } from "@/api/projects";
|
||||
import { spaceApi, type SpaceWorkbench } from "@/api/space";
|
||||
import { renderWithProviders } from "@/test/renderWithProviders";
|
||||
|
||||
|
|
@ -10,6 +12,17 @@ vi.mock("@/api/space", () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
vi.mock("@/api/projects", () => ({
|
||||
projectsApi: {
|
||||
getSummary: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@/api/pmflowOs", () => ({
|
||||
getPMFlowTruthState: vi.fn(),
|
||||
getPMFlowExecutionSummary: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@/features/pmflow-os/components/MobileBottomNav", () => ({
|
||||
MobileBottomNav: () => <nav aria-label="mobile bottom nav" />,
|
||||
}));
|
||||
|
|
@ -34,6 +47,10 @@ const emptyWorkbench: SpaceWorkbench = {
|
|||
};
|
||||
|
||||
describe("PersonalSpacePage", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("renders the read-only Space workbench sections without demo saved ideas", async () => {
|
||||
vi.mocked(spaceApi.getWorkbench).mockResolvedValue(emptyWorkbench);
|
||||
|
||||
|
|
@ -385,4 +402,196 @@ describe("PersonalSpacePage", () => {
|
|||
expect(screen.queryByRole("button", { name: /创建反馈|写入反馈|记录收入|运行合规/ })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: /生成草案|写入 ChangeSet|创建下一轮/ })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("aggregates reviewer routing into the next action queue as navigation-only review attention", async () => {
|
||||
vi.mocked(projectsApi.getSummary).mockResolvedValue({
|
||||
projects: [
|
||||
{
|
||||
id: 17,
|
||||
title: "真实项目",
|
||||
description: "真实项目摘要",
|
||||
status: "active",
|
||||
progress: 62,
|
||||
created_at: "2026-04-26T07:00:00.000Z",
|
||||
updated_at: "2026-04-26T08:00:00.000Z",
|
||||
},
|
||||
],
|
||||
} as never);
|
||||
vi.mocked(getPMFlowTruthState).mockResolvedValue({
|
||||
loopStage: "command_idle",
|
||||
lastWritebackAt: "2026-04-26T08:00:00.000Z",
|
||||
project: {
|
||||
id: "17",
|
||||
name: "真实项目",
|
||||
goal: "真实项目摘要",
|
||||
stage: "development",
|
||||
threadId: "thread-17",
|
||||
},
|
||||
thread: {
|
||||
id: "thread-17",
|
||||
title: "真实项目",
|
||||
projectId: "17",
|
||||
objective: "继续推进",
|
||||
extractionStatus: "idle",
|
||||
messageIds: [],
|
||||
},
|
||||
messages: [],
|
||||
changeSets: [],
|
||||
changeItems: [],
|
||||
evidence: [],
|
||||
requirements: [],
|
||||
decisions: [],
|
||||
tasks: [],
|
||||
risks: [],
|
||||
modelDecisions: [],
|
||||
testRuns: [],
|
||||
releases: [],
|
||||
metricSignals: [],
|
||||
approvals: [],
|
||||
feed: [],
|
||||
teamInbox: { product: [], engineering: [], qa: [], growth: [] },
|
||||
reviewerRouting: {
|
||||
status: "warning",
|
||||
warningCode: "reviewer_missing",
|
||||
summary: {
|
||||
totalPendingReview: 4,
|
||||
highRiskCount: 1,
|
||||
missingReviewerCount: 1,
|
||||
iterationDraftCount: 1,
|
||||
},
|
||||
items: [
|
||||
{
|
||||
projectId: "17",
|
||||
changeSetId: "cs-high",
|
||||
changeSetTitle: "支付风控策略变更",
|
||||
changeSetStatus: "open",
|
||||
sourceType: "ai_extraction",
|
||||
reviewReason: "高风险策略变更,需要负责人审查。",
|
||||
riskLevel: "high",
|
||||
priority: "urgent",
|
||||
changeTypes: ["Risk", "Decision"],
|
||||
itemCount: 3,
|
||||
pendingItemCount: 3,
|
||||
evidenceCount: 2,
|
||||
suggestedReviewer: { displayName: "陈雨", role: "OWNER", reason: "负责人", confidence: "high" },
|
||||
fallbackReviewerRole: "OWNER",
|
||||
targetUrl: "/change-review?projectId=17&changeSetId=cs-high",
|
||||
updatedAt: "2026-04-26T08:00:00.000Z",
|
||||
},
|
||||
{
|
||||
projectId: "17",
|
||||
changeSetId: "cs-returned",
|
||||
changeSetTitle: "增长实验埋点变更",
|
||||
changeSetStatus: "open",
|
||||
sourceType: "iteration_recommendation",
|
||||
reviewReason: "审查要求补充指标口径。",
|
||||
riskLevel: "medium",
|
||||
priority: "elevated",
|
||||
changeTypes: ["Requirement"],
|
||||
itemCount: 2,
|
||||
pendingItemCount: 1,
|
||||
evidenceCount: 1,
|
||||
suggestedReviewer: { displayName: "李宁", role: "REVIEWER", reason: "增长 reviewer", confidence: "medium" },
|
||||
fallbackReviewerRole: "REVIEWER",
|
||||
targetUrl: "/change-review?projectId=17&changeSetId=cs-returned",
|
||||
updatedAt: "2026-04-26T07:50:00.000Z",
|
||||
reviewDecision: "request_changes",
|
||||
},
|
||||
{
|
||||
projectId: "17",
|
||||
changeSetId: "cs-missing",
|
||||
changeSetTitle: "权限模型变更",
|
||||
changeSetStatus: "open",
|
||||
sourceType: "manual",
|
||||
reviewReason: "缺少明确 reviewer。",
|
||||
riskLevel: "medium",
|
||||
priority: "elevated",
|
||||
changeTypes: ["Decision"],
|
||||
itemCount: 1,
|
||||
pendingItemCount: 1,
|
||||
evidenceCount: 0,
|
||||
suggestedReviewer: null,
|
||||
fallbackReviewerRole: null,
|
||||
targetUrl: "",
|
||||
updatedAt: "2026-04-26T07:40:00.000Z",
|
||||
},
|
||||
{
|
||||
projectId: "17",
|
||||
changeSetId: "cs-normal",
|
||||
changeSetTitle: "帮助中心文案变更",
|
||||
changeSetStatus: "open",
|
||||
sourceType: "manual",
|
||||
reviewReason: "普通文案变更待审。",
|
||||
riskLevel: "low",
|
||||
priority: "normal",
|
||||
changeTypes: ["Requirement"],
|
||||
itemCount: 1,
|
||||
pendingItemCount: 1,
|
||||
evidenceCount: 1,
|
||||
suggestedReviewer: { displayName: "王敏", role: "REVIEWER", reason: "文案 reviewer", confidence: "medium" },
|
||||
fallbackReviewerRole: "REVIEWER",
|
||||
targetUrl: "/change-review?projectId=17&changeSetId=cs-normal",
|
||||
updatedAt: "2026-04-26T07:30:00.000Z",
|
||||
},
|
||||
],
|
||||
},
|
||||
activeChangeSetId: null,
|
||||
selectedTaskId: null,
|
||||
execution: {
|
||||
taskId: null,
|
||||
status: "synced",
|
||||
prompt: "",
|
||||
commitLog: [],
|
||||
prStatus: "",
|
||||
testSummary: "",
|
||||
writebackStatus: "",
|
||||
},
|
||||
} as never);
|
||||
vi.mocked(getPMFlowExecutionSummary).mockResolvedValue({
|
||||
projectId: "17",
|
||||
execution: {
|
||||
taskId: null,
|
||||
status: "synced",
|
||||
prompt: "",
|
||||
commitLog: [],
|
||||
prStatus: "",
|
||||
testSummary: "",
|
||||
writebackStatus: "",
|
||||
},
|
||||
releaseGate: {
|
||||
status: "empty",
|
||||
title: "",
|
||||
summary: "",
|
||||
blockingReasons: [],
|
||||
nextActions: [],
|
||||
checks: [],
|
||||
evidence: [],
|
||||
},
|
||||
devSync: {},
|
||||
repoBindings: [],
|
||||
activeDevTasks: [],
|
||||
recentProgressEvents: [],
|
||||
recentDeliveries: [],
|
||||
warnings: [],
|
||||
} as never);
|
||||
|
||||
const { spaceApi: actualSpaceApi } = await vi.importActual<typeof import("@/api/space")>("@/api/space");
|
||||
const workbench = await actualSpaceApi.getWorkbench();
|
||||
const reviewActions = workbench.nextActionQueue.filter((item) => item.kind === "review_attention");
|
||||
|
||||
expect(reviewActions.map((item) => item.title)).toEqual([
|
||||
"高风险 ChangeSet 待我审",
|
||||
"审查退回待处理",
|
||||
"审查人缺失",
|
||||
"ChangeSet 待我审",
|
||||
]);
|
||||
expect(reviewActions.map((item) => item.priorityRank)).toEqual([8, 14, 24, 32]);
|
||||
expect(reviewActions.map((item) => item.href)).toEqual([
|
||||
"/change-review?projectId=17&changeSetId=cs-high",
|
||||
"/change-review?projectId=17&changeSetId=cs-returned",
|
||||
"/team-layer?projectId=17",
|
||||
"/change-review?projectId=17&changeSetId=cs-normal",
|
||||
]);
|
||||
expect(reviewActions[2].summary).toContain("缺少可用审查人");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import {
|
|||
Search,
|
||||
ShieldCheck,
|
||||
Sparkles,
|
||||
UserCheck,
|
||||
} from "lucide-react";
|
||||
import type { ReactNode } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
|
@ -229,7 +230,7 @@ function MobileSpace({
|
|||
{data.nextActionQueue.length ? (
|
||||
data.nextActionQueue.slice(0, 6).map((item) => <MobileNextActionQueueItem key={item.id} item={item} />)
|
||||
) : (
|
||||
<InlineEmpty title="暂无下一步动作" description="没有真实待确认、执行回写、发布阻塞、发布后反馈或下一轮草案时,这里保持空态。" />
|
||||
<InlineEmpty title="暂无下一步动作" description="没有真实待审查、待确认、执行回写、发布阻塞、发布后反馈或下一轮草案时,这里保持空态。" />
|
||||
)}
|
||||
</div>
|
||||
</MobileSection>
|
||||
|
|
@ -383,7 +384,7 @@ function NextActionQueuePanel({ items }: { items: SpaceNextActionQueueItem[] })
|
|||
<section className="rounded-[20px] border border-[#cbd9ee] bg-white shadow-[0_18px_44px_rgba(15,23,42,0.07)]">
|
||||
<PanelHeader
|
||||
title="我的下一步"
|
||||
description="按风险和时效排序,只读聚合待确认、执行回写、发布阻塞、发布后反馈与下一轮草案;所有动作只跳主流程。"
|
||||
description="按风险和时效排序,只读聚合待审查、待确认、执行回写、发布阻塞、发布后反馈与下一轮草案;所有动作只跳主流程。"
|
||||
badge={items.length}
|
||||
className="px-5 pt-5"
|
||||
/>
|
||||
|
|
@ -391,7 +392,7 @@ function NextActionQueuePanel({ items }: { items: SpaceNextActionQueueItem[] })
|
|||
{items.length ? (
|
||||
items.slice(0, 8).map((item, index) => <NextActionQueueRow key={item.id} item={item} rank={index + 1} />)
|
||||
) : (
|
||||
<InlineEmpty title="暂无下一步动作" description="没有真实待确认、执行回写、发布阻塞、发布后反馈或下一轮草案时,这里保持空态;不会用 mock 冒充。" />
|
||||
<InlineEmpty title="暂无下一步动作" description="没有真实待审查、待确认、执行回写、发布阻塞、发布后反馈或下一轮草案时,这里保持空态;不会用 mock 冒充。" />
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
|
|
@ -1150,6 +1151,7 @@ function nextActionTone(item: SpaceNextActionQueueItem): { iconClass: string; ba
|
|||
|
||||
function NextActionIcon({ kind }: { kind: SpaceNextActionKind }) {
|
||||
if (kind === "pending_confirmation") return <ShieldCheck className="h-5 w-5" />;
|
||||
if (kind === "review_attention") return <UserCheck className="h-5 w-5" />;
|
||||
if (kind === "release_blocked") return <AlertCircle className="h-5 w-5" />;
|
||||
if (kind === "execution_attention") return <Sparkles className="h-5 w-5" />;
|
||||
if (kind === "iteration_draft") return <FileText className="h-5 w-5" />;
|
||||
|
|
@ -1157,6 +1159,7 @@ function NextActionIcon({ kind }: { kind: SpaceNextActionKind }) {
|
|||
}
|
||||
|
||||
function nextActionCta(item: SpaceNextActionQueueItem): string {
|
||||
if (item.kind === "review_attention") return item.href.startsWith("/team-layer") ? "看审查" : "去审查";
|
||||
if (item.href.startsWith("/change-review")) return "去确认";
|
||||
if (item.href.startsWith("/execution-bridge")) return "看执行";
|
||||
if (item.href.startsWith("/release-growth")) return item.kind === "feedback_signal" ? "看反馈" : "看发布";
|
||||
|
|
|
|||
Loading…
Reference in New Issue