feat(space): surface release validation attention
This commit is contained in:
parent
199be13681
commit
b9b4b5a306
|
|
@ -807,6 +807,36 @@ P0 公共壳冻结已完成,下一批可以进入 `Batch P1` 三模块并行
|
||||||
|
|
||||||
## 8. 进度台账
|
## 8. 进度台账
|
||||||
|
|
||||||
|
### 2026-04-26 / space-p3-validation-run-attention / implemented
|
||||||
|
|
||||||
|
完成内容:
|
||||||
|
|
||||||
|
- 在 `frontend/src/api/space.ts` 将 `releaseAttention` 从单纯 release gate 关注升级为 `truth-state.releases` 发布验证投影 + `execution-summary.releaseGate` 的只读聚合。
|
||||||
|
- `/space` 桌面端和移动端“发布关注”区块现在优先显示“发布验证阻塞 / 发布验证待确认”;没有发布验证投影时才回落显示“发布候选 / 发布门禁阻塞”。
|
||||||
|
- 发布关注项只展示来源标签、验证/门禁状态、检查项摘要、阻塞原因和更新时间;点击只跳转 `/release-growth?projectId=<id>`。
|
||||||
|
- 发布验证缺失时显示“暂无发布验证记录”空态,不使用 mock 或默认成功结果冒充真实验证。
|
||||||
|
- 本轮不创建 validation run、不写 ProgressEvent、不写 ReleaseValidationRun、不新增后端接口、不执行发布命令。
|
||||||
|
|
||||||
|
涉及文件:
|
||||||
|
|
||||||
|
- `frontend/src/api/space.ts`
|
||||||
|
- `frontend/src/features/personal-space/PersonalSpacePage.tsx`
|
||||||
|
- `frontend/src/features/personal-space/PersonalSpacePage.test.tsx`
|
||||||
|
- `docs/architecture/CODE_REFERENCE_INDEX.md`
|
||||||
|
- `docs/architecture/ARCHITECTURE_INVENTORY.md`
|
||||||
|
- `docs/PMFLOW_PRODUCT_DELIVERY_STANDARD.md`
|
||||||
|
|
||||||
|
验证:
|
||||||
|
|
||||||
|
- `cd frontend && npm run test -- src/features/personal-space/PersonalSpacePage.test.tsx` 通过:`2 passed`;仍有既有 React Router v7 future flag warning。
|
||||||
|
- `cd frontend && npm run typecheck` 通过。
|
||||||
|
- `cd frontend && npm run build` 通过;Vite 仍提示既有 manual chunk circular warning,但构建成功。
|
||||||
|
|
||||||
|
未完成或风险:
|
||||||
|
|
||||||
|
- Space P3 只消费 release validation 投影,不负责创建/更新 release validation run;真实运行记录仍由 Release/Growth lane 维护。
|
||||||
|
- 当前 Space 侧使用 `truth-state.releases` 的状态和 gateItems 表达验证结果;若后续 PMFlow OS 暴露独立 `validationRuns` 合同,需要由 Space lane 再切换到更精确字段。
|
||||||
|
|
||||||
### 2026-04-26 / big-module-integration-p3 / done
|
### 2026-04-26 / big-module-integration-p3 / done
|
||||||
|
|
||||||
完成内容:
|
完成内容:
|
||||||
|
|
|
||||||
|
|
@ -194,7 +194,7 @@
|
||||||
- AI Steward 问答浮窗现分三层显性化:`FloatingQAChat.tsx` 负责即时回答/历史回放归一化,`QAConversationStatusBar.tsx` 展示对象/来源/仓库/执行通道/plan handoff,`QAResponseMetaCard.tsx` 展示 `fit_brief / external_recommendation / recommended_next_step`,对应前端回归由 `FloatingQAChat.test.tsx` 与 `QAResponseMetaCard.test.tsx` 锁定。
|
- AI Steward 问答浮窗现分三层显性化:`FloatingQAChat.tsx` 负责即时回答/历史回放归一化,`QAConversationStatusBar.tsx` 展示对象/来源/仓库/执行通道/plan handoff,`QAResponseMetaCard.tsx` 展示 `fit_brief / external_recommendation / recommended_next_step`,对应前端回归由 `FloatingQAChat.test.tsx` 与 `QAResponseMetaCard.test.tsx` 锁定。
|
||||||
- 登录页与营销页仍共享 `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`。
|
- 登录页与营销页仍共享 `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`。
|
- 前端主包治理已切到“同步首屏最小集 + 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`。
|
||||||
- `frontend/src/features/personal-space/PersonalSpacePage.tsx` 是空间首页视图层,`frontend/src/api/space.ts` 是空间工作面只读聚合 API:从 `projects` summary、PMFlow OS `truth-state` 与 `execution-summary` 生成继续推进、待确认、发布关注、执行中 / AI 正在推进、暂存想法、草案箱、最近项目摘要;Space P2 的 `releaseAttention` 只消费 `execution-summary.releaseGate`,把测试失败、PR 未合并、发布门禁阻塞或可发布候选显示到 `/space`,项目主动作进入 `/pmflow-os?projectId=<id>`,待确认项进入 `/change-review?projectId=<id>&changeSetId=<id>`,发布关注项进入 `/release-growth?projectId=<id>`,执行中项进入 `/execution-bridge?projectId=<id>`;暂存想法后端只读聚合尚未接入时只显示 empty/warning,不使用 mock 数据冒充真实内容;移动端为空间首页独立 App 布局,不直接压缩桌面卡片,并保留最近项目区块;`frontend/src/pages/PersonalSpacePage.tsx` 是薄入口。该域只表达个人空间和主流程导航,不做团队空间强切换、企业组织管理、Project Memory 写入、ChangeSet 状态写入、ProgressEvent 写入或发布执行。
|
- `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` 的发布候选或发布门禁阻塞;发布验证缺失时显示明确空态,不虚构验证结果。项目主动作进入 `/pmflow-os?projectId=<id>`,待确认项进入 `/change-review?projectId=<id>&changeSetId=<id>`,发布关注项进入 `/release-growth?projectId=<id>`,执行中项进入 `/execution-bridge?projectId=<id>`;暂存想法后端只读聚合尚未接入时只显示 empty/warning,不使用 mock 数据冒充真实内容;移动端为空间首页独立 App 布局,不直接压缩桌面卡片,并保留最近项目区块;`frontend/src/pages/PersonalSpacePage.tsx` 是薄入口。该域只表达个人空间和主流程导航,不做团队空间强切换、企业组织管理、Project Memory 写入、ChangeSet 状态写入、ProgressEvent 写入、ReleaseValidationRun 写入或发布执行。
|
||||||
- `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/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 和失败提醒显示/静默状态,不代表邮件、桌面推送或系统通知中心已接入。`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/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 和失败提醒显示/静默状态,不代表邮件、桌面推送或系统通知中心已接入。`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 均调用 `/pmflow-os` 后端并 refetch;只有显式 `?demo=1` 或 `?dataSource=demo` 时才启用本地 mock 演示状态机。真实读取/动作失败会进入错误/空态,不静默 fallback 成 mock 成功。
|
- `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 均调用 `/pmflow-os` 后端并 refetch;只有显式 `?demo=1` 或 `?dataSource=demo` 时才启用本地 mock 演示状态机。真实读取/动作失败会进入错误/空态,不静默 fallback 成 mock 成功。
|
||||||
|
|
|
||||||
|
|
@ -97,14 +97,14 @@
|
||||||
- PMFlow UI 契约真源:先读 `docs/PMFLOW_UI_CONTRACT.md`,再修改 `project-launch`、`personal-space`、`pmflow-os` 相关页面;该文档锁定 Logo、色彩、移动端底部导航、状态标签、AI 操作/用户动作/待确认视觉语法和核心功能 canonical 契约
|
- PMFlow UI 契约真源:先读 `docs/PMFLOW_UI_CONTRACT.md`,再修改 `project-launch`、`personal-space`、`pmflow-os` 相关页面;该文档锁定 Logo、色彩、移动端底部导航、状态标签、AI 操作/用户动作/待确认视觉语法和核心功能 canonical 契约
|
||||||
- PMFlow 大模块并行公共壳:参考 `docs/PMFLOW_PRODUCT_DELIVERY_STANDARD.md` 的 `4.4.1-4.4.3` 与 `frontend/src/router/index.tsx`、`frontend/src/main.tsx`、`frontend/src/features/pmflow-os/components/MobileBottomNav.tsx`、`frontend/src/features/pmflow-os/components/PMFlowOsFrame.tsx`;`/space` 归 Space lane,`/pmflow-os / /command-center / /change-review / /execution-bridge / /release-growth` 归 Main Flow lane,`/me` 归 Account Settings lane,`/settings` 当前仅为 reserved route,新增或改动必须由 Shell / Integration owner 串行处理
|
- PMFlow 大模块并行公共壳:参考 `docs/PMFLOW_PRODUCT_DELIVERY_STANDARD.md` 的 `4.4.1-4.4.3` 与 `frontend/src/router/index.tsx`、`frontend/src/main.tsx`、`frontend/src/features/pmflow-os/components/MobileBottomNav.tsx`、`frontend/src/features/pmflow-os/components/PMFlowOsFrame.tsx`;`/space` 归 Space lane,`/pmflow-os / /command-center / /change-review / /execution-bridge / /release-growth` 归 Main Flow lane,`/me` 归 Account Settings lane,`/settings` 当前仅为 reserved route,新增或改动必须由 Shell / Integration owner 串行处理
|
||||||
- PMFlow OS 产品合同真源:先读 `docs/architecture/PMFLOW_OS_PRODUCT_CONTRACT.md`,再实现 `pmflow-os` 真实后端接入;该文档锁定 `ProjectTruthState / Thread / ChangeSet / ChangeItem / Evidence / Approval / Project Memory / ExecutionContext` 与 `truth_nodes / tasks / semantic_facts / ai_project_control / dev_sync` 的复用边界和后续 API 草案
|
- PMFlow OS 产品合同真源:先读 `docs/architecture/PMFLOW_OS_PRODUCT_CONTRACT.md`,再实现 `pmflow-os` 真实后端接入;该文档锁定 `ProjectTruthState / Thread / ChangeSet / ChangeItem / Evidence / Approval / Project Memory / ExecutionContext` 与 `truth_nodes / tasks / semantic_facts / ai_project_control / dev_sync` 的复用边界和后续 API 草案
|
||||||
- 新增前端 API 调用:参考 `frontend/src/api/sstc.ts`;Space 工作面只读聚合参考 `frontend/src/api/space.ts`,只消费项目摘要与 PMFlow OS `truth-state / execution-summary`,不写 Project Memory、ChangeSet 状态或 execution event;Space P2 已从 `PMFlowExecutionSummary.releaseGate` 派生 `releaseAttention`,用于在 `/space` 显示测试失败、PR 未合并、发布门禁阻塞或可发布候选,所有动作只跳转 `/release-growth?projectId=<id>`;PMFlow OS 真实接入参考 `frontend/src/api/pmflowOs.ts` 的 `getPMFlowTruthState(projectId)`、`getPMFlowExecutionSummary(projectId)`、`createPMFlowExecutionHandoff`、`consumePMFlowExecutionHandoff`、`createPMFlowExecutionProgressEvent`、`createPMFlowThreadMessage`、`createPMFlowExtraction`、`getPMFlowChangeSet`、`acceptPMFlowChangeItem`、`editPMFlowChangeItem`、`deferPMFlowChangeItem`、`commitPMFlowChangeSet`;`PMFlowExecutionSummary.releaseGate` 是 ReleaseGrowth 真实发布门禁前端合同;Project Launch 创建链参考 `frontend/src/api/pmflowLaunch.ts` 的 `createPMFlowLaunchDraft` 与 `confirmPMFlowLaunchDraft`
|
- 新增前端 API 调用:参考 `frontend/src/api/sstc.ts`;Space 工作面只读聚合参考 `frontend/src/api/space.ts`,只消费项目摘要与 PMFlow OS `truth-state / execution-summary`,不写 Project Memory、ChangeSet 状态、ProgressEvent 或 ReleaseValidationRun;Space P3 的 `releaseAttention` 优先从 `truth-state.releases` 的 release validation 投影生成“发布验证阻塞 / 发布验证待确认”,没有发布验证投影时才回落到 `PMFlowExecutionSummary.releaseGate` 的“发布候选 / 发布门禁阻塞”,所有动作只跳转 `/release-growth?projectId=<id>`;PMFlow OS 真实接入参考 `frontend/src/api/pmflowOs.ts` 的 `getPMFlowTruthState(projectId)`、`getPMFlowExecutionSummary(projectId)`、`createPMFlowExecutionHandoff`、`consumePMFlowExecutionHandoff`、`createPMFlowExecutionProgressEvent`、`createPMFlowThreadMessage`、`createPMFlowExtraction`、`getPMFlowChangeSet`、`acceptPMFlowChangeItem`、`editPMFlowChangeItem`、`deferPMFlowChangeItem`、`commitPMFlowChangeSet`;`PMFlowExecutionSummary.releaseGate` 是 ReleaseGrowth 真实发布门禁前端合同;Project Launch 创建链参考 `frontend/src/api/pmflowLaunch.ts` 的 `createPMFlowLaunchDraft` 与 `confirmPMFlowLaunchDraft`
|
||||||
- AI Steward 页面入口:参考 `frontend/src/pages/ai-steward/`
|
- AI Steward 页面入口:参考 `frontend/src/pages/ai-steward/`
|
||||||
- AI Steward 问答浮窗与上下文显性化组件:参考 `frontend/src/components/ai-steward/FloatingQAChat.tsx`、`frontend/src/components/ai-steward/QAConversationStatusBar.tsx`、`frontend/src/components/ai-steward/QAResponseMetaCard.tsx`
|
- AI Steward 问答浮窗与上下文显性化组件:参考 `frontend/src/components/ai-steward/FloatingQAChat.tsx`、`frontend/src/components/ai-steward/QAConversationStatusBar.tsx`、`frontend/src/components/ai-steward/QAResponseMetaCard.tsx`
|
||||||
- AI Steward QA contract 前端类型(含 `analysis_packet`):参考 `frontend/src/api/aiSteward.ts`
|
- AI Steward QA contract 前端类型(含 `analysis_packet`):参考 `frontend/src/api/aiSteward.ts`
|
||||||
- AI Steward handoff 元卡片回归:参考 `frontend/src/components/ai-steward/FloatingQAChat.test.tsx`、`frontend/src/components/ai-steward/QAResponseMetaCard.test.tsx`
|
- AI Steward handoff 元卡片回归:参考 `frontend/src/components/ai-steward/FloatingQAChat.test.tsx`、`frontend/src/components/ai-steward/QAResponseMetaCard.test.tsx`
|
||||||
- AI Steward / QA session 前端 contract:参考 `frontend/src/api/aiSteward.ts`
|
- AI Steward / QA session 前端 contract:参考 `frontend/src/api/aiSteward.ts`
|
||||||
- PMFlow OS 前端 MVP 产品域:参考 `frontend/src/features/pmflow-os/`
|
- PMFlow OS 前端 MVP 产品域:参考 `frontend/src/features/pmflow-os/`
|
||||||
- 个人空间首页 / 空间层入口前端产品域:参考 `frontend/src/features/personal-space/`、`frontend/src/api/space.ts` 与薄入口 `frontend/src/pages/PersonalSpacePage.tsx`;本地预览路由入口为 `/space`,覆盖继续推进、待我确认、发布关注、执行中 / AI 正在推进、暂存想法、草案箱与最近项目,点击项目主动作进入 `/pmflow-os?projectId=<id>`,待确认项进入 `/change-review?projectId=<id>&changeSetId=<id>`,执行中项进入 `/execution-bridge?projectId=<id>`,发布关注项进入 `/release-growth?projectId=<id>`;Space 只读聚合项目摘要、PMFlow OS `truth-state / execution-summary.releaseGate`,不直接写 Project Memory、ChangeSet 状态或 execution event;暂存想法后端只读聚合尚未接入时必须显示 empty/warning,不得用 mock 数据冒充真实内容;空间层共享左侧栏、顶部条与空间用量组件在 `frontend/src/features/personal-space/components/SpaceChrome.tsx`,`/project-launch` 桌面首次新建壳也复用该组件
|
- 个人空间首页 / 空间层入口前端产品域:参考 `frontend/src/features/personal-space/`、`frontend/src/api/space.ts` 与薄入口 `frontend/src/pages/PersonalSpacePage.tsx`;本地预览路由入口为 `/space`,覆盖继续推进、待我确认、发布关注、执行中 / AI 正在推进、暂存想法、草案箱与最近项目,点击项目主动作进入 `/pmflow-os?projectId=<id>`,待确认项进入 `/change-review?projectId=<id>&changeSetId=<id>`,执行中项进入 `/execution-bridge?projectId=<id>`,发布关注项进入 `/release-growth?projectId=<id>`;Space 只读聚合项目摘要、PMFlow OS `truth-state.releases / execution-summary.releaseGate`,发布验证缺失时显示明确空态,不虚构验证结果,不直接写 Project Memory、ChangeSet 状态、execution event 或 ReleaseValidationRun;暂存想法后端只读聚合尚未接入时必须显示 empty/warning,不得用 mock 数据冒充真实内容;空间层共享左侧栏、顶部条与空间用量组件在 `frontend/src/features/personal-space/components/SpaceChrome.tsx`,`/project-launch` 桌面首次新建壳也复用该组件
|
||||||
- 新项目启动台 / 首次进入页前端产品域:参考 `frontend/src/features/project-launch/` 与薄入口 `frontend/src/pages/ProjectLaunchPage.tsx`;真实模式默认通过 `frontend/src/api/pmflowLaunch.ts` 调用后端 draft/confirm API,只有显式 `?demo=1` 或 `?dataSource=demo` 才启用 `mockData.ts` 本地演示状态机;本地预览路由入口为 `/project-launch`
|
- 新项目启动台 / 首次进入页前端产品域:参考 `frontend/src/features/project-launch/` 与薄入口 `frontend/src/pages/ProjectLaunchPage.tsx`;真实模式默认通过 `frontend/src/api/pmflowLaunch.ts` 调用后端 draft/confirm API,只有显式 `?demo=1` 或 `?dataSource=demo` 才启用 `mockData.ts` 本地演示状态机;本地预览路由入口为 `/project-launch`
|
||||||
- Account Settings / `/me` 前端产品域与 P1/P2 偏好合同:参考 `frontend/src/features/account-settings/AccountSettingsPanel.tsx`、`frontend/src/features/pmflow-os/pages/AccountPage.tsx`、`frontend/src/api/accountSettings.ts`、`backend/app/api/account_settings_api/`、`backend/app/schemas/account_settings.py` 与 `backend/app/services/account_settings_service.py`;当前覆盖个人资料、默认语言、AI 回答深度、证据展示偏好、待确认、执行回写、发布门禁和失败提醒通知偏好,移动端使用分组列表与 bottom sheet,账户退出仍复用 `AccountControls.tsx`,不改 OAuth/JWT/auth core;P1 后端合同提供 `GET /account-settings/me` 与 `PUT /account-settings/me`,已由 `backend/app/api/router.py` 挂入核心路由组,按当前 `tenant_id + user_id` 保存账号偏好,前端有 token 时优先账号同步,后端不可用时明确回落 `仅本机暂存 / 保存失败`;P2 在 `frontend/src/api/accountSettings.ts` 提供 `shouldShowAccountNotification` 与 `getAccountNotificationConsumption`,把通知偏好映射为页面内提醒消费模型,只控制本地 UI 的待确认、执行回写、发布门禁和失败提醒展示,不代表真实邮件/桌面推送已接入
|
- Account Settings / `/me` 前端产品域与 P1/P2 偏好合同:参考 `frontend/src/features/account-settings/AccountSettingsPanel.tsx`、`frontend/src/features/pmflow-os/pages/AccountPage.tsx`、`frontend/src/api/accountSettings.ts`、`backend/app/api/account_settings_api/`、`backend/app/schemas/account_settings.py` 与 `backend/app/services/account_settings_service.py`;当前覆盖个人资料、默认语言、AI 回答深度、证据展示偏好、待确认、执行回写、发布门禁和失败提醒通知偏好,移动端使用分组列表与 bottom sheet,账户退出仍复用 `AccountControls.tsx`,不改 OAuth/JWT/auth core;P1 后端合同提供 `GET /account-settings/me` 与 `PUT /account-settings/me`,已由 `backend/app/api/router.py` 挂入核心路由组,按当前 `tenant_id + user_id` 保存账号偏好,前端有 token 时优先账号同步,后端不可用时明确回落 `仅本机暂存 / 保存失败`;P2 在 `frontend/src/api/accountSettings.ts` 提供 `shouldShowAccountNotification` 与 `getAccountNotificationConsumption`,把通知偏好映射为页面内提醒消费模型,只控制本地 UI 的待确认、执行回写、发布门禁和失败提醒展示,不代表真实邮件/桌面推送已接入
|
||||||
- PMFlow OS 共享 truth store 与主链状态动作:参考 `frontend/src/features/pmflow-os/store.tsx`;当前支持 `?projectId=<id>` 走 React Query 读取真实 `truth-state` 与 `execution-summary`,真实模式下 thread message、`proposed_items` extraction、ChangeItem `accept/edit/defer`、ChangeSet commit、Execution Handoff create/consume、execution progress writeback 均调后端并 refetch,不本地 promote 入 Project Memory 或伪造 execution event;`?demo=1` 才启用本地 mock 演示状态机和本地动作;项目内 AI 结构化闭环状态机为 `command_idle / ai_extracting / extraction_ready / review_open / writeback_success`
|
- PMFlow OS 共享 truth store 与主链状态动作:参考 `frontend/src/features/pmflow-os/store.tsx`;当前支持 `?projectId=<id>` 走 React Query 读取真实 `truth-state` 与 `execution-summary`,真实模式下 thread message、`proposed_items` extraction、ChangeItem `accept/edit/defer`、ChangeSet commit、Execution Handoff create/consume、execution progress writeback 均调后端并 refetch,不本地 promote 入 Project Memory 或伪造 execution event;`?demo=1` 才启用本地 mock 演示状态机和本地动作;项目内 AI 结构化闭环状态机为 `command_idle / ai_extracting / extraction_ready / review_open / writeback_success`
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import type {
|
||||||
ChangeItem,
|
ChangeItem,
|
||||||
PMFlowExecutionStatus,
|
PMFlowExecutionStatus,
|
||||||
PMFlowObjectStatus,
|
PMFlowObjectStatus,
|
||||||
|
Release,
|
||||||
ProjectTruthState,
|
ProjectTruthState,
|
||||||
} from "@/features/pmflow-os/types";
|
} from "@/features/pmflow-os/types";
|
||||||
|
|
||||||
|
|
@ -51,6 +52,8 @@ export interface SpaceReleaseAttentionItem {
|
||||||
id: string;
|
id: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
projectTitle: string;
|
projectTitle: string;
|
||||||
|
source: "release_gate" | "release_validation";
|
||||||
|
sourceLabel: string;
|
||||||
title: string;
|
title: string;
|
||||||
summary: string;
|
summary: string;
|
||||||
gateStatus: "ready" | "blocked" | "needs_confirmation";
|
gateStatus: "ready" | "blocked" | "needs_confirmation";
|
||||||
|
|
@ -337,7 +340,7 @@ function deriveAIProgress(snapshot: ProjectSnapshot): SpaceAIProgressItem[] {
|
||||||
|
|
||||||
function releaseAttentionTitle(status: SpaceReleaseAttentionItem["gateStatus"]): string {
|
function releaseAttentionTitle(status: SpaceReleaseAttentionItem["gateStatus"]): string {
|
||||||
if (status === "blocked") return "发布门禁阻塞";
|
if (status === "blocked") return "发布门禁阻塞";
|
||||||
if (status === "ready") return "可发布候选待确认";
|
if (status === "ready") return "发布候选";
|
||||||
return "发布需要人工确认";
|
return "发布需要人工确认";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -372,7 +375,73 @@ function releaseAttentionReasons(execution: PMFlowExecutionSummary): string[] {
|
||||||
return [...gate.blockingReasons, ...checkReasons].filter(Boolean).slice(0, 4);
|
return [...gate.blockingReasons, ...checkReasons].filter(Boolean).slice(0, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
function deriveReleaseAttention(snapshot: ProjectSnapshot): SpaceReleaseAttentionItem[] {
|
function releaseValidationStatus(release: Release): SpaceReleaseAttentionItem["gateStatus"] | null {
|
||||||
|
const hasFailedGateItem = release.gateItems.some((item) => !item.passed);
|
||||||
|
if (hasFailedGateItem) return "blocked";
|
||||||
|
if (release.status === "ready" || release.status === "shipping" || release.status === "live" || release.status === "watch") {
|
||||||
|
return "needs_confirmation";
|
||||||
|
}
|
||||||
|
if (release.status === "check_pending" && release.gateItems.length > 0) return "needs_confirmation";
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function releaseValidationTitle(status: SpaceReleaseAttentionItem["gateStatus"]): string {
|
||||||
|
if (status === "blocked") return "发布验证阻塞";
|
||||||
|
return "发布验证待确认";
|
||||||
|
}
|
||||||
|
|
||||||
|
function releaseValidationSummary(release: Release, status: SpaceReleaseAttentionItem["gateStatus"]): string {
|
||||||
|
if (release.summary) return release.summary;
|
||||||
|
if (status === "blocked") return "发布验证记录存在未通过检查,需要回到 Release Growth 处理阻塞项。";
|
||||||
|
return "发布验证已有记录,需要人工确认后再推进发布判断。";
|
||||||
|
}
|
||||||
|
|
||||||
|
function releaseValidationReasons(release: Release): string[] {
|
||||||
|
return release.gateItems
|
||||||
|
.filter((item) => !item.passed)
|
||||||
|
.map((item) => `${item.label}: 未通过`)
|
||||||
|
.slice(0, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
function deriveValidationAttention(snapshot: ProjectSnapshot): SpaceReleaseAttentionItem[] {
|
||||||
|
const { project, truth } = snapshot;
|
||||||
|
if (!truth) return [];
|
||||||
|
const projectId = String(project.id);
|
||||||
|
const projectTitle = project.title || truth.project.name || `项目 ${projectId}`;
|
||||||
|
|
||||||
|
return truth.releases
|
||||||
|
.map((release): SpaceReleaseAttentionItem | null => {
|
||||||
|
const gateStatus = releaseValidationStatus(release);
|
||||||
|
if (!gateStatus) return null;
|
||||||
|
const failedItems = releaseValidationReasons(release);
|
||||||
|
const passedCount = release.gateItems.filter((item) => item.passed).length;
|
||||||
|
const evidenceLabels = [
|
||||||
|
"发布验证",
|
||||||
|
release.status === "ready" ? "验证通过" : null,
|
||||||
|
gateStatus === "blocked" ? "检查阻塞" : null,
|
||||||
|
release.gateItems.length ? `${passedCount}/${release.gateItems.length} 检查项通过` : null,
|
||||||
|
].filter((item): item is string => Boolean(item));
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: `release-validation-${projectId}-${release.id}`,
|
||||||
|
projectId,
|
||||||
|
projectTitle,
|
||||||
|
source: "release_validation" as const,
|
||||||
|
sourceLabel: "发布验证记录",
|
||||||
|
title: releaseValidationTitle(gateStatus),
|
||||||
|
summary: releaseValidationSummary(release, gateStatus),
|
||||||
|
gateStatus,
|
||||||
|
severity: releaseAttentionSeverity(gateStatus),
|
||||||
|
reasons: failedItems,
|
||||||
|
evidenceLabels,
|
||||||
|
updatedAt: project.updated_at,
|
||||||
|
href: releaseGrowthHref(projectId),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((item): item is SpaceReleaseAttentionItem => Boolean(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
function deriveGateAttention(snapshot: ProjectSnapshot): SpaceReleaseAttentionItem[] {
|
||||||
const { project, truth, execution } = snapshot;
|
const { project, truth, execution } = snapshot;
|
||||||
const gate = execution?.releaseGate;
|
const gate = execution?.releaseGate;
|
||||||
if (!gate || gate.status === "empty") return [];
|
if (!gate || gate.status === "empty") return [];
|
||||||
|
|
@ -392,6 +461,8 @@ function deriveReleaseAttention(snapshot: ProjectSnapshot): SpaceReleaseAttentio
|
||||||
id: `release-attention-${projectId}`,
|
id: `release-attention-${projectId}`,
|
||||||
projectId,
|
projectId,
|
||||||
projectTitle,
|
projectTitle,
|
||||||
|
source: "release_gate",
|
||||||
|
sourceLabel: "发布门禁",
|
||||||
title: gate.title || releaseAttentionTitle(gateStatus),
|
title: gate.title || releaseAttentionTitle(gateStatus),
|
||||||
summary: gate.summary || fallbackSummary,
|
summary: gate.summary || fallbackSummary,
|
||||||
gateStatus,
|
gateStatus,
|
||||||
|
|
@ -404,6 +475,12 @@ function deriveReleaseAttention(snapshot: ProjectSnapshot): SpaceReleaseAttentio
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function deriveReleaseAttention(snapshot: ProjectSnapshot): SpaceReleaseAttentionItem[] {
|
||||||
|
const validationAttention = deriveValidationAttention(snapshot);
|
||||||
|
if (validationAttention.length) return validationAttention;
|
||||||
|
return deriveGateAttention(snapshot);
|
||||||
|
}
|
||||||
|
|
||||||
function deriveDrafts(snapshot: ProjectSnapshot): SpaceDraftItem[] {
|
function deriveDrafts(snapshot: ProjectSnapshot): SpaceDraftItem[] {
|
||||||
const { project, truth } = snapshot;
|
const { project, truth } = snapshot;
|
||||||
if (!truth) return [];
|
if (!truth) return [];
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ describe("PersonalSpacePage", () => {
|
||||||
expect(screen.getAllByText("继续推进")).not.toHaveLength(0);
|
expect(screen.getAllByText("继续推进")).not.toHaveLength(0);
|
||||||
expect(screen.getAllByText("待我确认")).not.toHaveLength(0);
|
expect(screen.getAllByText("待我确认")).not.toHaveLength(0);
|
||||||
expect(screen.getAllByText("发布关注")).not.toHaveLength(0);
|
expect(screen.getAllByText("发布关注")).not.toHaveLength(0);
|
||||||
|
expect(screen.getAllByText("暂无发布验证记录")).not.toHaveLength(0);
|
||||||
expect(screen.getAllByText("AI 正在推进")).not.toHaveLength(0);
|
expect(screen.getAllByText("AI 正在推进")).not.toHaveLength(0);
|
||||||
expect(screen.getAllByText("暂存想法")).not.toHaveLength(0);
|
expect(screen.getAllByText("暂存想法")).not.toHaveLength(0);
|
||||||
expect(screen.getAllByText("草案箱")).not.toHaveLength(0);
|
expect(screen.getAllByText("草案箱")).not.toHaveLength(0);
|
||||||
|
|
@ -101,15 +102,47 @@ describe("PersonalSpacePage", () => {
|
||||||
id: "release-attention-17",
|
id: "release-attention-17",
|
||||||
projectId: "17",
|
projectId: "17",
|
||||||
projectTitle: "真实项目",
|
projectTitle: "真实项目",
|
||||||
title: "发布门禁阻塞",
|
source: "release_validation",
|
||||||
|
sourceLabel: "发布验证记录",
|
||||||
|
title: "发布验证阻塞",
|
||||||
summary: "测试失败,PR 未合并,不能发布。",
|
summary: "测试失败,PR 未合并,不能发布。",
|
||||||
gateStatus: "blocked",
|
gateStatus: "blocked",
|
||||||
severity: "high",
|
severity: "high",
|
||||||
reasons: ["测试失败: typecheck 未通过", "PR: 未合并"],
|
reasons: ["测试失败: typecheck 未通过", "PR: 未合并"],
|
||||||
evidenceLabels: ["PR", "测试失败"],
|
evidenceLabels: ["发布验证", "检查阻塞", "1/2 检查项通过"],
|
||||||
updatedAt: "2026-04-26T08:00:00.000Z",
|
updatedAt: "2026-04-26T08:00:00.000Z",
|
||||||
href: "/release-growth?projectId=17",
|
href: "/release-growth?projectId=17",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "release-validation-18",
|
||||||
|
projectId: "18",
|
||||||
|
projectTitle: "待确认项目",
|
||||||
|
source: "release_validation",
|
||||||
|
sourceLabel: "发布验证记录",
|
||||||
|
title: "发布验证待确认",
|
||||||
|
summary: "发布验证已通过,需要人工确认。",
|
||||||
|
gateStatus: "needs_confirmation",
|
||||||
|
severity: "medium",
|
||||||
|
reasons: [],
|
||||||
|
evidenceLabels: ["发布验证", "验证通过"],
|
||||||
|
updatedAt: "2026-04-26T08:00:00.000Z",
|
||||||
|
href: "/release-growth?projectId=18",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "release-gate-19",
|
||||||
|
projectId: "19",
|
||||||
|
projectTitle: "候选项目",
|
||||||
|
source: "release_gate",
|
||||||
|
sourceLabel: "发布门禁",
|
||||||
|
title: "发布候选",
|
||||||
|
summary: "执行证据已进入发布判断。",
|
||||||
|
gateStatus: "ready",
|
||||||
|
severity: "low",
|
||||||
|
reasons: [],
|
||||||
|
evidenceLabels: ["测试通过", "PR"],
|
||||||
|
updatedAt: "2026-04-26T08:00:00.000Z",
|
||||||
|
href: "/release-growth?projectId=19",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
drafts: [
|
drafts: [
|
||||||
{
|
{
|
||||||
|
|
@ -156,10 +189,19 @@ describe("PersonalSpacePage", () => {
|
||||||
"href",
|
"href",
|
||||||
"/execution-bridge?projectId=17"
|
"/execution-bridge?projectId=17"
|
||||||
);
|
);
|
||||||
expect(screen.getAllByRole("link", { name: /发布门禁阻塞/ })[0]).toHaveAttribute(
|
expect(screen.getAllByRole("link", { name: /发布验证阻塞/ })[0]).toHaveAttribute(
|
||||||
"href",
|
"href",
|
||||||
"/release-growth?projectId=17"
|
"/release-growth?projectId=17"
|
||||||
);
|
);
|
||||||
|
expect(screen.getAllByRole("link", { name: /发布验证待确认/ })[0]).toHaveAttribute(
|
||||||
|
"href",
|
||||||
|
"/release-growth?projectId=18"
|
||||||
|
);
|
||||||
|
expect(screen.getAllByRole("link", { name: /发布候选/ })[0]).toHaveAttribute(
|
||||||
|
"href",
|
||||||
|
"/release-growth?projectId=19"
|
||||||
|
);
|
||||||
expect(screen.getAllByText("测试失败: typecheck 未通过")).not.toHaveLength(0);
|
expect(screen.getAllByText("测试失败: typecheck 未通过")).not.toHaveLength(0);
|
||||||
|
expect(screen.queryByRole("button", { name: /创建发布验证|运行发布验证|重新验证/ })).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -237,7 +237,7 @@ function MobileSpace({
|
||||||
{data.releaseAttention.length ? (
|
{data.releaseAttention.length ? (
|
||||||
data.releaseAttention.slice(0, 4).map((item) => <MobileReleaseAttention key={item.id} item={item} />)
|
data.releaseAttention.slice(0, 4).map((item) => <MobileReleaseAttention key={item.id} item={item} />)
|
||||||
) : (
|
) : (
|
||||||
<InlineEmpty title="暂无发布关注" description="有测试失败、PR 未确认或发布门禁阻塞时会显示在这里。" />
|
<InlineEmpty title="暂无发布验证记录" description="有真实发布验证、测试失败、PR 未确认或发布门禁阻塞时会显示在这里;不会虚构验证结果。" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</MobileSection>
|
</MobileSection>
|
||||||
|
|
@ -435,7 +435,7 @@ function ReleaseAttentionPanel({ items }: { items: SpaceReleaseAttentionItem[] }
|
||||||
<section className="rounded-[20px] border border-[#dde6f2] bg-white">
|
<section className="rounded-[20px] border border-[#dde6f2] bg-white">
|
||||||
<PanelHeader
|
<PanelHeader
|
||||||
title="发布关注"
|
title="发布关注"
|
||||||
description="只读聚合 execution-summary 的发布门禁;处理动作回到 Release Growth 主流程。"
|
description="只读聚合发布验证记录和 execution-summary 门禁;处理动作回到 Release Growth 主流程。"
|
||||||
badge={items.length}
|
badge={items.length}
|
||||||
className="px-5 pt-5"
|
className="px-5 pt-5"
|
||||||
/>
|
/>
|
||||||
|
|
@ -443,7 +443,7 @@ function ReleaseAttentionPanel({ items }: { items: SpaceReleaseAttentionItem[] }
|
||||||
{items.length ? (
|
{items.length ? (
|
||||||
items.slice(0, 5).map((item) => <ReleaseAttentionRow key={item.id} item={item} />)
|
items.slice(0, 5).map((item) => <ReleaseAttentionRow key={item.id} item={item} />)
|
||||||
) : (
|
) : (
|
||||||
<InlineEmpty title="暂无发布关注" description="有测试失败、PR 未合并或发布门禁阻塞时会显示在这里。" />
|
<InlineEmpty title="暂无发布验证记录" description="有真实发布验证、测试失败、PR 未合并或发布门禁阻塞时会显示在这里;不会虚构验证结果。" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
@ -464,7 +464,7 @@ function ReleaseAttentionRow({ item }: { item: SpaceReleaseAttentionItem }) {
|
||||||
</span>
|
</span>
|
||||||
<span className="mt-1 block line-clamp-2 text-xs leading-5 text-slate-500">{item.summary}</span>
|
<span className="mt-1 block line-clamp-2 text-xs leading-5 text-slate-500">{item.summary}</span>
|
||||||
<span className="mt-1 block truncate text-xs text-slate-500">
|
<span className="mt-1 block truncate text-xs text-slate-500">
|
||||||
{item.projectTitle} · {item.evidenceLabels.length ? item.evidenceLabels.join(" / ") : "缺少可用证据"} · {formatRelativeTime(item.updatedAt)}
|
{item.projectTitle} · {item.sourceLabel} · {item.evidenceLabels.length ? item.evidenceLabels.join(" / ") : "缺少可用证据"} · {formatRelativeTime(item.updatedAt)}
|
||||||
</span>
|
</span>
|
||||||
{item.reasons.length ? <span className="mt-1 block truncate text-xs font-medium text-rose-600">{item.reasons[0]}</span> : null}
|
{item.reasons.length ? <span className="mt-1 block truncate text-xs font-medium text-rose-600">{item.reasons[0]}</span> : null}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -714,7 +714,7 @@ function MobileReleaseAttention({ item }: { item: SpaceReleaseAttentionItem }) {
|
||||||
<span className="min-w-0 flex-1">
|
<span className="min-w-0 flex-1">
|
||||||
<span className="block truncate text-sm font-semibold text-slate-950">{item.title}</span>
|
<span className="block truncate text-sm font-semibold text-slate-950">{item.title}</span>
|
||||||
<span className="mt-1 block line-clamp-2 text-xs leading-5 text-slate-500">{item.summary}</span>
|
<span className="mt-1 block line-clamp-2 text-xs leading-5 text-slate-500">{item.summary}</span>
|
||||||
<span className="mt-2 block truncate text-xs text-slate-500">{item.projectTitle} · {item.evidenceLabels.join(" / ") || "缺少可用证据"}</span>
|
<span className="mt-2 block truncate text-xs text-slate-500">{item.projectTitle} · {item.sourceLabel} · {item.evidenceLabels.join(" / ") || "缺少可用证据"}</span>
|
||||||
</span>
|
</span>
|
||||||
<span className={cn("shrink-0 rounded-md px-2 py-1 text-xs font-semibold", tone.badgeClass)}>{releaseGateLabel(item.gateStatus)}</span>
|
<span className={cn("shrink-0 rounded-md px-2 py-1 text-xs font-semibold", tone.badgeClass)}>{releaseGateLabel(item.gateStatus)}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue