feat(space): surface release attention read model
This commit is contained in:
parent
e90adbc2eb
commit
9ba9876811
|
|
@ -844,6 +844,36 @@ P0 公共壳冻结已完成,下一批可以进入 `Batch P1` 三模块并行
|
|||
- ReleaseGrowth P1 只生成发布判断和下一步动作,不触发真实部署、不写 release validation run。
|
||||
- 增长反馈、收入、ROI 和 compliance 的真实发布后反馈循环仍待后续 Packet 接入。
|
||||
|
||||
### 2026-04-26 / space-p2-release-attention-readmodel / implemented
|
||||
|
||||
完成内容:
|
||||
|
||||
- 在 `frontend/src/api/space.ts` 新增 `releaseAttention` 只读聚合,从已有项目摘要、`truth-state` 与 `execution-summary.releaseGate` 派生个人空间发布关注项。
|
||||
- `/space` 桌面端和移动端新增“发布关注”区块,显示测试失败、PR 未合并、发布门禁阻塞、发布候选或需要人工确认的项目。
|
||||
- 发布关注项只展示 release gate 状态、阻塞原因、证据标签和更新时间;点击只跳转 `/release-growth?projectId=<id>`。
|
||||
- 本轮不写 Project Memory、不写 ChangeSet、不写 ProgressEvent、不新增后端接口、不执行发布命令。
|
||||
- 暂存想法仍保持真实 empty/warning,不使用 mock 冒充。
|
||||
|
||||
涉及文件:
|
||||
|
||||
- `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 P2 依赖 Main Flow `release-growth-p1-execution-evidence-gate` 的 `execution-summary.releaseGate` 合同;最终合并需确保该合同先进入 integration base。
|
||||
- 本轮只做个人空间 read model,不做发布验证运行、真实部署、增长反馈或发布后指标聚合。
|
||||
|
||||
### 2026-04-26 / big-module-integration-p2 / done
|
||||
|
||||
完成内容:
|
||||
|
|
@ -1033,6 +1063,36 @@ P0 公共壳冻结已完成,下一批可以进入 `Batch P1` 三模块并行
|
|||
|
||||
- 暂存想法真实只读聚合后端合同尚未实现;当前 Space 明确暴露为空态和 warning,不使用演示数据兜底。
|
||||
|
||||
### 2026-04-26 / Account P2 Notification Preference Consumption / implemented
|
||||
|
||||
完成内容:
|
||||
|
||||
- `frontend/src/api/accountSettings.ts` 新增通知偏好消费模型,将 `pendingConfirmation / executionWriteback / releaseGate / failureAlert` 映射到 `待确认中心 / Execution Bridge / Release Growth / 全局失败提醒` 四类页面内提醒。
|
||||
- 新增 `shouldShowAccountNotification` 与 `getAccountNotificationConsumption`,用于本地 UI 判断页面提醒显示或静默;不接入真实邮件、桌面推送或系统级通知中心。
|
||||
- 修正通知偏好合并逻辑:旧本机缓存或后端返回缺字段时,按默认偏好逐项补齐,避免浅合并导致 `desktop/emailDigest` 丢失。
|
||||
- `AccountSettingsPanel` 在通知偏好区域展示“页面内提醒消费说明”,明确哪些开关会影响待确认、执行回写、发布门禁和失败提醒,也明确桌面/邮件仅记录偏好、不代表系统级推送已接入。
|
||||
- 测试覆盖通知偏好读取、保存、消费模型和 fallback。
|
||||
|
||||
涉及文件:
|
||||
|
||||
- `frontend/src/api/accountSettings.ts`
|
||||
- `frontend/src/features/account-settings/AccountSettingsPanel.tsx`
|
||||
- `frontend/src/features/account-settings/accountSettings.test.ts`
|
||||
- `docs/architecture/ARCHITECTURE_INVENTORY.md`
|
||||
- `docs/architecture/CODE_REFERENCE_INDEX.md`
|
||||
- `docs/PMFLOW_PRODUCT_DELIVERY_STANDARD.md`
|
||||
|
||||
验证:
|
||||
|
||||
- `cd frontend && npm run test -- src/features/account-settings/accountSettings.test.ts` 通过:`7 passed`。
|
||||
- `cd frontend && npm run typecheck` 通过。
|
||||
- `cd frontend && npm run build` 通过;Vite 仍输出既有 manual chunk circular warning,未阻断构建。
|
||||
|
||||
未完成或风险:
|
||||
|
||||
- 本轮只在 Account Settings 合同和本地 UI 消费层落地通知偏好,不修改 PMFlow OS truth store、router、JWT/OAuth/auth core,也不新增后端表。
|
||||
- 由于本包 write scope 不包含 `ExecutionBridgePage / ReleaseGrowthPage / ChangeReviewPage`,本轮没有跨页直接接入这些页面;后续如要让各页面读取该 helper,应单独开对应 Main Flow/Integration packet。
|
||||
|
||||
### 2026-04-26 / Account P1 Preferences Backend Contract / implemented
|
||||
|
||||
完成内容:
|
||||
|
|
|
|||
|
|
@ -194,9 +194,9 @@
|
|||
- 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`。
|
||||
- 前端主包治理已切到“同步首屏最小集 + 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 正在推进、暂存想法、草案箱、最近项目摘要;项目主动作进入 `/pmflow-os?projectId=<id>`,待确认项进入 `/change-review?projectId=<id>&changeSetId=<id>`,执行中项进入 `/execution-bridge?projectId=<id>`;暂存想法后端只读聚合尚未接入时只显示 empty/warning,不使用 mock 数据冒充真实内容;移动端为空间首页独立 App 布局,不直接压缩桌面卡片,并保留最近项目区块;`frontend/src/pages/PersonalSpacePage.tsx` 是薄入口。该域只表达个人空间和主流程导航,不做团队空间强切换、企业组织管理、Project Memory 写入、ChangeSet 状态写入或 execution event 写入。
|
||||
- `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/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/`,失败时回落浏览器本机暂存。`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` 仍保留演示用项目内 AI 结构化闭环状态机:`command_idle / ai_extracting / extraction_ready / review_open / writeback_success`。`frontend/src/features/pmflow-os/pages/StructuredExtractionPage.tsx` 是本轮结构化提取页,`ChangeReviewPage.tsx` 是决策与待确认中心,`MemoryWritebackResultPage.tsx` 是项目记忆写入结果页,`WorkGraphPage.tsx` 是项目工作台关系总览与对象投影视图,`ExecutionBridgePage.tsx` 是执行桥上下文包、交接按钮、运行端领取确认、PR/测试与回写状态页;`ReleaseGrowthPage.tsx` 真实模式读取 `execution-summary.releaseGate`,从 branch、commit、PR、test、failed evidence 展示 `可发布 / 阻塞 / 需要人工确认 / 缺少证据` 判断、阻塞原因和下一步动作,不执行发布命令、不展示 mock 发布结果;静态发布/增长原型只保留在显式 demo 模式。A3 已接入后端 truth-state,C1 已接入真实 message/extraction/review 状态机,C2 已接入真实 ChangeSet commit,Phase D 已接入真实 execution-summary/dev-sync,D.2 已接入 `POST /execution-handoffs` 创建 PromptPackage + handoff_created progress event,P1 已接入 `POST /execution-handoffs/{prompt_package_id}/consume` 创建 handoff_consumed progress event 并把 execution status 投影为 coding,Release Growth P1 已接入 execution evidence gate;当前仍不调用真实 AI 提取,不把 mock 当真实失败 fallback。`ProjectMemoryPage.tsx` 在真实模式下展示后端 `truth_nodes + semantic_facts` 的 `projectMemory` 投影,accepted/edited ChangeItem 只有 commit 成功后才进入 Project Memory。`frontend/src/features/pmflow-os/components/ProjectStructuredLoop.tsx` 提供 `StatusLabel / EvidenceSnippet / ObjectChangeCard / ProjectConversationPanel / StructuredExtractionPanel / ProjectAICopilotPanel` 等闭环组件,并复用契约化 `InlineNotice` 表达“未确认不得写入”的局部提示。
|
||||
- PMFlow OS 后端入口位于 `backend/app/api/pmflow_os_api/`,projection service 位于 `backend/app/services/pmflow_os_projection_service.py`,Change Review / commit 服务位于 `backend/app/services/pmflow_change_review_service.py`,Pydantic 合同位于 `backend/app/schemas/pmflow_os.py`;当前聚合 `truth_nodes / tasks / semantic_facts / ai_project_control / dev_sync`,`Thread / Message` 来自 `pmflow_threads / pmflow_thread_messages`,`ChangeSet / ChangeItem / Approval / EvidenceLink` 来自 `backend/app/models/pmflow_change_review.py` 对应真实表。C1 已实现 message、`proposed_items` extraction、ChangeSet detail、ChangeItem `accept/edit/defer`;C2 backend/frontend 已实现 `POST /pmflow-os/change-sets/{change_set_id}/commit` 与真实模式前端接入,默认 atomic,将 `Requirement / Decision / Risk` 写入 `truth_nodes`,将 `Task` 写入 `truth_nodes(node_type='task') + tasks`,高风险项强制 approval gate;D.2 已实现 `POST /pmflow-os/projects/{project_id}/execution-handoffs`,校验 task/repo project 边界,生成/复用 `PromptPackage(package_kind='execution_handoff')` 并写 `ProgressEvent(event_type='handoff_created')` 起点;P1 已实现 `POST /pmflow-os/projects/{project_id}/execution-handoffs/{prompt_package_id}/consume`,校验 execution handoff package 后更新 `PromptPackage.package_payload.latest_handoff.handoff_state=consumed` 并写 `ProgressEvent(event_type='handoff_consumed', derived_task_status='coding')`;Release Growth P1 在 `ExecutionSummaryProjection.releaseGate` 中从 `ProgressEvent` 推导发布门禁状态,失败事件 fail-closed 阻塞发布,无证据返回 empty/warning;这些步骤都不直接执行本机命令或真实发布命令;`ModelDecision / MetricSignal` 在 C2 V1 仍 fail-closed。
|
||||
|
|
|
|||
|
|
@ -97,22 +97,22 @@
|
|||
- 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 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 或 execution event;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 状态或 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`
|
||||
- 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 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 / QA session 前端 contract:参考 `frontend/src/api/aiSteward.ts`
|
||||
- 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>`;Space 只读聚合项目摘要、PMFlow OS `truth-state / execution-summary`,不直接写 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 / execution-summary.releaseGate`,不直接写 Project Memory、ChangeSet 状态或 execution event;暂存想法后端只读聚合尚未接入时必须显示 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`
|
||||
- Account Settings / `/me` 前端产品域与 P1 后端同步合同:参考 `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 时优先账号同步,后端不可用时明确回落 `仅本机暂存 / 保存失败`,不得假装云端账号保存
|
||||
- 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 产品页入口:参考 `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`;`ReleaseGrowthPage.tsx` 真实模式只展示 `execution-summary.releaseGate`、execution evidence、阻塞原因和下一步动作,缺证据时显示 empty/warning,不展示 mock 发布结果,静态高保真发布/增长数据仅保留在显式 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`
|
||||
- Project Launch 真实/演示分支回归测试:参考 `frontend/src/features/project-launch/useProjectLaunchState.test.ts`
|
||||
- Account Settings 前端合同回归测试:参考 `frontend/src/features/account-settings/accountSettings.test.ts`
|
||||
- Account Settings 前端合同回归测试:参考 `frontend/src/features/account-settings/accountSettings.test.ts`,覆盖默认偏好、账号同步 fallback、通知偏好消费模型和缺字段 fallback。
|
||||
- 网页问答显式锚点与 AI Steward URL 构造:参考 `frontend/src/components/ai-steward/qaPageAnchor.ts`
|
||||
- Inbox 页面入口:参考 `frontend/src/pages/inbox/`
|
||||
- Inbox `plan_mode` 展开卡片与映射:参考 `frontend/src/components/inbox/InboxListItem.tsx`、`frontend/src/lib/inbox.ts`、`frontend/src/api/aiPmo.ts`
|
||||
|
|
|
|||
|
|
@ -47,6 +47,20 @@ export interface SpaceAIProgressItem {
|
|||
href: string;
|
||||
}
|
||||
|
||||
export interface SpaceReleaseAttentionItem {
|
||||
id: string;
|
||||
projectId: string;
|
||||
projectTitle: string;
|
||||
title: string;
|
||||
summary: string;
|
||||
gateStatus: "ready" | "blocked" | "needs_confirmation";
|
||||
severity: "low" | "medium" | "high";
|
||||
reasons: string[];
|
||||
evidenceLabels: string[];
|
||||
updatedAt: string;
|
||||
href: string;
|
||||
}
|
||||
|
||||
export interface SpaceDraftItem {
|
||||
id: string;
|
||||
projectId: string;
|
||||
|
|
@ -71,6 +85,7 @@ export interface SpaceWorkbench {
|
|||
projects: SpaceWorkbenchProject[];
|
||||
pendingConfirmations: SpacePendingConfirmation[];
|
||||
aiProgress: SpaceAIProgressItem[];
|
||||
releaseAttention: SpaceReleaseAttentionItem[];
|
||||
savedIdeas: SpaceSavedIdea[];
|
||||
drafts: SpaceDraftItem[];
|
||||
recentProjects: SpaceWorkbenchProject[];
|
||||
|
|
@ -169,6 +184,10 @@ function executionHref(projectId: string): string {
|
|||
return `/execution-bridge?projectId=${encodeURIComponent(projectId)}`;
|
||||
}
|
||||
|
||||
function releaseGrowthHref(projectId: string): string {
|
||||
return `/release-growth?projectId=${encodeURIComponent(projectId)}`;
|
||||
}
|
||||
|
||||
function pendingChangeItems(truth: ProjectTruthState | null): ChangeItem[] {
|
||||
if (!truth) return [];
|
||||
return truth.changeItems.filter((item) => item.status === "pending_review");
|
||||
|
|
@ -316,6 +335,75 @@ function deriveAIProgress(snapshot: ProjectSnapshot): SpaceAIProgressItem[] {
|
|||
return items;
|
||||
}
|
||||
|
||||
function releaseAttentionTitle(status: SpaceReleaseAttentionItem["gateStatus"]): string {
|
||||
if (status === "blocked") return "发布门禁阻塞";
|
||||
if (status === "ready") return "可发布候选待确认";
|
||||
return "发布需要人工确认";
|
||||
}
|
||||
|
||||
function releaseAttentionSeverity(status: SpaceReleaseAttentionItem["gateStatus"]): SpaceReleaseAttentionItem["severity"] {
|
||||
if (status === "blocked") return "high";
|
||||
if (status === "ready") return "low";
|
||||
return "medium";
|
||||
}
|
||||
|
||||
function releaseEvidenceLabel(type: string): string {
|
||||
const labels: Record<string, string> = {
|
||||
branch_created: "分支",
|
||||
commit: "提交",
|
||||
pr_opened: "PR",
|
||||
pr_synced: "PR 同步",
|
||||
test_passed: "测试通过",
|
||||
checks_passed: "检查通过",
|
||||
test_failed: "测试失败",
|
||||
failed: "失败事件",
|
||||
blocked: "阻塞事件",
|
||||
merged: "已合并",
|
||||
accepted: "已确认",
|
||||
};
|
||||
return labels[type] || type;
|
||||
}
|
||||
|
||||
function releaseAttentionReasons(execution: PMFlowExecutionSummary): string[] {
|
||||
const gate = execution.releaseGate;
|
||||
const checkReasons = gate.checks
|
||||
.filter((check) => check.status !== "passed")
|
||||
.map((check) => `${check.label}: ${check.detail || (check.status === "failed" ? "未通过" : "缺少证据")}`);
|
||||
return [...gate.blockingReasons, ...checkReasons].filter(Boolean).slice(0, 4);
|
||||
}
|
||||
|
||||
function deriveReleaseAttention(snapshot: ProjectSnapshot): SpaceReleaseAttentionItem[] {
|
||||
const { project, truth, execution } = snapshot;
|
||||
const gate = execution?.releaseGate;
|
||||
if (!gate || gate.status === "empty") return [];
|
||||
|
||||
const projectId = String(project.id);
|
||||
const gateStatus = gate.status;
|
||||
const projectTitle = project.title || truth?.project.name || `项目 ${projectId}`;
|
||||
const reasons = releaseAttentionReasons(execution);
|
||||
const evidenceLabels = Array.from(new Set(gate.evidence.map((item) => releaseEvidenceLabel(item.type)))).slice(0, 5);
|
||||
const fallbackSummary =
|
||||
gateStatus === "blocked"
|
||||
? "执行证据显示发布门禁被阻塞,需要回到发布增长页查看失败事件。"
|
||||
: "执行证据已进入发布判断,需要人工确认后再推进。";
|
||||
|
||||
return [
|
||||
{
|
||||
id: `release-attention-${projectId}`,
|
||||
projectId,
|
||||
projectTitle,
|
||||
title: gate.title || releaseAttentionTitle(gateStatus),
|
||||
summary: gate.summary || fallbackSummary,
|
||||
gateStatus,
|
||||
severity: releaseAttentionSeverity(gateStatus),
|
||||
reasons,
|
||||
evidenceLabels,
|
||||
updatedAt: gate.updatedAt || project.updated_at,
|
||||
href: releaseGrowthHref(projectId),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function deriveDrafts(snapshot: ProjectSnapshot): SpaceDraftItem[] {
|
||||
const { project, truth } = snapshot;
|
||||
if (!truth) return [];
|
||||
|
|
@ -382,6 +470,7 @@ export const spaceApi = {
|
|||
projects: workbenchProjects.slice(0, 4),
|
||||
pendingConfirmations: sortByDate(snapshots.flatMap(derivePending)).slice(0, 8),
|
||||
aiProgress: sortByDate(snapshots.flatMap(deriveAIProgress)).slice(0, 8),
|
||||
releaseAttention: sortByDate(snapshots.flatMap(deriveReleaseAttention)).slice(0, 8),
|
||||
savedIdeas: [],
|
||||
drafts: sortByDate(snapshots.flatMap(deriveDrafts)).slice(0, 8),
|
||||
recentProjects: sortByDate(workbenchProjects),
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ const emptyWorkbench: SpaceWorkbench = {
|
|||
projects: [],
|
||||
pendingConfirmations: [],
|
||||
aiProgress: [],
|
||||
releaseAttention: [],
|
||||
savedIdeas: [],
|
||||
drafts: [],
|
||||
recentProjects: [],
|
||||
|
|
@ -39,6 +40,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("AI 正在推进")).not.toHaveLength(0);
|
||||
expect(screen.getAllByText("暂存想法")).not.toHaveLength(0);
|
||||
expect(screen.getAllByText("草案箱")).not.toHaveLength(0);
|
||||
|
|
@ -94,6 +96,21 @@ describe("PersonalSpacePage", () => {
|
|||
href: "/execution-bridge?projectId=17",
|
||||
},
|
||||
],
|
||||
releaseAttention: [
|
||||
{
|
||||
id: "release-attention-17",
|
||||
projectId: "17",
|
||||
projectTitle: "真实项目",
|
||||
title: "发布门禁阻塞",
|
||||
summary: "测试失败,PR 未合并,不能发布。",
|
||||
gateStatus: "blocked",
|
||||
severity: "high",
|
||||
reasons: ["测试失败: typecheck 未通过", "PR: 未合并"],
|
||||
evidenceLabels: ["PR", "测试失败"],
|
||||
updatedAt: "2026-04-26T08:00:00.000Z",
|
||||
href: "/release-growth?projectId=17",
|
||||
},
|
||||
],
|
||||
drafts: [
|
||||
{
|
||||
id: "17-需求-draft-1",
|
||||
|
|
@ -139,5 +156,10 @@ describe("PersonalSpacePage", () => {
|
|||
"href",
|
||||
"/execution-bridge?projectId=17"
|
||||
);
|
||||
expect(screen.getAllByRole("link", { name: /发布门禁阻塞/ })[0]).toHaveAttribute(
|
||||
"href",
|
||||
"/release-growth?projectId=17"
|
||||
);
|
||||
expect(screen.getAllByText("测试失败: typecheck 未通过")).not.toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import {
|
|||
type SpaceAIProgressItem,
|
||||
type SpaceDraftItem,
|
||||
type SpacePendingConfirmation,
|
||||
type SpaceReleaseAttentionItem,
|
||||
type SpaceSavedIdea,
|
||||
type SpaceWorkbenchProject,
|
||||
} from "@/api/space";
|
||||
|
|
@ -105,6 +106,7 @@ function DesktopSpace({
|
|||
projectCount={data?.recentProjects.length ?? 0}
|
||||
pendingCount={data?.pendingConfirmations.length ?? 0}
|
||||
aiCount={data?.aiProgress.length ?? 0}
|
||||
releaseCount={data?.releaseAttention.length ?? 0}
|
||||
/>
|
||||
|
||||
{isLoading ? <WorkbenchLoading /> : error ? <WorkbenchError error={error} onRetry={onRetry} /> : null}
|
||||
|
|
@ -137,6 +139,8 @@ function DesktopSpace({
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<ReleaseAttentionPanel items={data.releaseAttention} />
|
||||
|
||||
<div className="grid gap-5 xl:grid-cols-3">
|
||||
<SavedIdeasBox ideas={data.savedIdeas} />
|
||||
<DraftBox drafts={data.drafts} />
|
||||
|
|
@ -195,8 +199,8 @@ function MobileSpace({
|
|||
</div>
|
||||
<div className="mt-5 grid grid-cols-4 gap-2 text-center">
|
||||
<MobileMetric label="待确认" value={data?.pendingConfirmations.length ?? 0} />
|
||||
<MobileMetric label="发布" value={data?.releaseAttention.length ?? 0} />
|
||||
<MobileMetric label="AI 推进" value={data?.aiProgress.length ?? 0} />
|
||||
<MobileMetric label="想法" value={data?.savedIdeas.length ?? 0} />
|
||||
<MobileMetric label="草案" value={data?.drafts.length ?? 0} />
|
||||
</div>
|
||||
</section>
|
||||
|
|
@ -228,6 +232,16 @@ function MobileSpace({
|
|||
</div>
|
||||
</MobileSection>
|
||||
|
||||
<MobileSection title="发布关注" badge={data.releaseAttention.length}>
|
||||
<div className="space-y-3">
|
||||
{data.releaseAttention.length ? (
|
||||
data.releaseAttention.slice(0, 4).map((item) => <MobileReleaseAttention key={item.id} item={item} />)
|
||||
) : (
|
||||
<InlineEmpty title="暂无发布关注" description="有测试失败、PR 未确认或发布门禁阻塞时会显示在这里。" />
|
||||
)}
|
||||
</div>
|
||||
</MobileSection>
|
||||
|
||||
<MobileSection title="AI 正在推进" badge={data.aiProgress.length}>
|
||||
<div className="space-y-3">
|
||||
{data.aiProgress.length ? (
|
||||
|
|
@ -282,12 +296,14 @@ function WorkbenchHeader({
|
|||
projectCount,
|
||||
pendingCount,
|
||||
aiCount,
|
||||
releaseCount,
|
||||
}: {
|
||||
generatedAt?: string;
|
||||
isFetching: boolean;
|
||||
projectCount: number;
|
||||
pendingCount: number;
|
||||
aiCount: number;
|
||||
releaseCount: number;
|
||||
}) {
|
||||
return (
|
||||
<section className="rounded-[24px] border border-[#dbe5f2] bg-white px-6 py-5 shadow-[0_14px_40px_rgba(15,23,42,0.06)]">
|
||||
|
|
@ -302,9 +318,10 @@ function WorkbenchHeader({
|
|||
聚合继续推进、待确认、执行中、暂存想法、草案箱和最近项目;点击项目后回到主流程处理。
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid min-w-[420px] grid-cols-3 gap-3">
|
||||
<div className="grid min-w-[520px] grid-cols-4 gap-3">
|
||||
<MetricTile label="最近项目" value={projectCount} />
|
||||
<MetricTile label="待确认" value={pendingCount} tone="orange" />
|
||||
<MetricTile label="发布关注" value={releaseCount} tone="rose" />
|
||||
<MetricTile label="执行中" value={aiCount} tone="blue" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -413,6 +430,49 @@ function AIProgressRail({ items }: { items: SpaceAIProgressItem[] }) {
|
|||
);
|
||||
}
|
||||
|
||||
function ReleaseAttentionPanel({ items }: { items: SpaceReleaseAttentionItem[] }) {
|
||||
return (
|
||||
<section className="rounded-[20px] border border-[#dde6f2] bg-white">
|
||||
<PanelHeader
|
||||
title="发布关注"
|
||||
description="只读聚合 execution-summary 的发布门禁;处理动作回到 Release Growth 主流程。"
|
||||
badge={items.length}
|
||||
className="px-5 pt-5"
|
||||
/>
|
||||
<div className="mt-4 divide-y divide-[#eef3f8]">
|
||||
{items.length ? (
|
||||
items.slice(0, 5).map((item) => <ReleaseAttentionRow key={item.id} item={item} />)
|
||||
) : (
|
||||
<InlineEmpty title="暂无发布关注" description="有测试失败、PR 未合并或发布门禁阻塞时会显示在这里。" />
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function ReleaseAttentionRow({ item }: { item: SpaceReleaseAttentionItem }) {
|
||||
const tone = releaseAttentionTone(item);
|
||||
return (
|
||||
<Link to={item.href} className="grid grid-cols-[44px_minmax(0,1fr)_120px] items-center gap-4 px-5 py-4 transition hover:bg-[#f8fbff]">
|
||||
<span className={cn("flex h-10 w-10 items-center justify-center rounded-xl", tone.iconClass)}>
|
||||
<AlertCircle className="h-5 w-5" />
|
||||
</span>
|
||||
<span className="min-w-0">
|
||||
<span className="flex items-center gap-2">
|
||||
<span className="truncate text-sm font-semibold text-slate-950">{item.title}</span>
|
||||
<span className={cn("shrink-0 rounded-md px-2 py-0.5 text-xs font-semibold", tone.badgeClass)}>{releaseGateLabel(item.gateStatus)}</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 truncate text-xs text-slate-500">
|
||||
{item.projectTitle} · {item.evidenceLabels.length ? item.evidenceLabels.join(" / ") : "缺少可用证据"} · {formatRelativeTime(item.updatedAt)}
|
||||
</span>
|
||||
{item.reasons.length ? <span className="mt-1 block truncate text-xs font-medium text-rose-600">{item.reasons[0]}</span> : null}
|
||||
</span>
|
||||
<span className="justify-self-end rounded-lg border border-[#cfe0ff] px-3 py-1.5 text-xs font-semibold text-[#2463EB]">查看门禁</span>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
function SavedIdeasBox({ ideas }: { ideas: SpaceSavedIdea[] }) {
|
||||
return (
|
||||
<section className="rounded-[20px] border border-[#dbe5f2] bg-white p-5">
|
||||
|
|
@ -558,8 +618,8 @@ function PanelHeader({
|
|||
);
|
||||
}
|
||||
|
||||
function MetricTile({ label, value, tone = "slate" }: { label: string; value: number; tone?: "slate" | "orange" | "blue" }) {
|
||||
const toneClass = tone === "orange" ? "text-orange-500" : tone === "blue" ? "text-[#2463EB]" : "text-slate-950";
|
||||
function MetricTile({ label, value, tone = "slate" }: { label: string; value: number; tone?: "slate" | "orange" | "blue" | "rose" }) {
|
||||
const toneClass = tone === "orange" ? "text-orange-500" : tone === "blue" ? "text-[#2463EB]" : tone === "rose" ? "text-rose-600" : "text-slate-950";
|
||||
return (
|
||||
<div className="rounded-2xl border border-[#e3eaf5] bg-[#fbfcff] px-4 py-3">
|
||||
<div className="text-xs text-slate-500">{label}</div>
|
||||
|
|
@ -643,6 +703,26 @@ function MobileAIProgress({ item }: { item: SpaceAIProgressItem }) {
|
|||
);
|
||||
}
|
||||
|
||||
function MobileReleaseAttention({ item }: { item: SpaceReleaseAttentionItem }) {
|
||||
const tone = releaseAttentionTone(item);
|
||||
return (
|
||||
<Link to={item.href} className="block rounded-[18px] border border-[#dbe5f2] bg-white p-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<span className={cn("flex h-10 w-10 shrink-0 items-center justify-center rounded-xl", tone.iconClass)}>
|
||||
<AlertCircle className="h-5 w-5" />
|
||||
</span>
|
||||
<span className="min-w-0 flex-1">
|
||||
<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-2 block truncate text-xs text-slate-500">{item.projectTitle} · {item.evidenceLabels.join(" / ") || "缺少可用证据"}</span>
|
||||
</span>
|
||||
<span className={cn("shrink-0 rounded-md px-2 py-1 text-xs font-semibold", tone.badgeClass)}>{releaseGateLabel(item.gateStatus)}</span>
|
||||
</div>
|
||||
{item.reasons.length ? <div className="mt-3 rounded-xl bg-rose-50 px-3 py-2 text-xs font-medium leading-5 text-rose-700">{item.reasons[0]}</div> : null}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
function MobileRecentProjectRow({ project }: { project: SpaceWorkbenchProject }) {
|
||||
return (
|
||||
<Link to={project.href} className="flex items-center justify-between gap-3 py-3 first:pt-3 last:pb-3">
|
||||
|
|
@ -754,6 +834,34 @@ function executionStatusLabel(status: SpaceAIProgressItem["status"]): string {
|
|||
return labels[status] || "进行中";
|
||||
}
|
||||
|
||||
function releaseGateLabel(status: SpaceReleaseAttentionItem["gateStatus"]): string {
|
||||
const labels: Record<SpaceReleaseAttentionItem["gateStatus"], string> = {
|
||||
blocked: "阻塞",
|
||||
needs_confirmation: "待确认",
|
||||
ready: "可发布",
|
||||
};
|
||||
return labels[status];
|
||||
}
|
||||
|
||||
function releaseAttentionTone(item: SpaceReleaseAttentionItem): { iconClass: string; badgeClass: string } {
|
||||
if (item.severity === "high") {
|
||||
return {
|
||||
iconClass: "bg-rose-50 text-rose-600",
|
||||
badgeClass: "bg-rose-50 text-rose-600",
|
||||
};
|
||||
}
|
||||
if (item.severity === "medium") {
|
||||
return {
|
||||
iconClass: "bg-orange-50 text-orange-600",
|
||||
badgeClass: "bg-orange-50 text-orange-600",
|
||||
};
|
||||
}
|
||||
return {
|
||||
iconClass: "bg-emerald-50 text-emerald-600",
|
||||
badgeClass: "bg-emerald-50 text-emerald-600",
|
||||
};
|
||||
}
|
||||
|
||||
function formatRelativeTime(value?: string): string {
|
||||
if (!value) return "未记录时间";
|
||||
const timestamp = new Date(value).getTime();
|
||||
|
|
|
|||
Loading…
Reference in New Issue