From bf697b5e28fc5b74e7bcca916b9e1f65f000995a Mon Sep 17 00:00:00 2001
From: lanrtop
Date: Thu, 2 Jul 2026 14:51:43 +0900
Subject: [PATCH] =?UTF-8?q?feat(ui):=20=E5=8A=9F=E8=83=BD=E5=8C=BA?=
=?UTF-8?q?=E9=87=8D=E6=9E=84=E9=98=B6=E6=AE=B5=E4=BA=8C=E2=80=94=E2=80=94?=
=?UTF-8?q?=E6=B4=9E=E5=AF=9F=E9=A1=B5=20+=20=E5=85=A8=E5=B1=80/=E9=A1=B9?=
=?UTF-8?q?=E7=9B=AE=E5=B1=82=E7=BA=A7=E5=BD=92=E4=BD=8D=EF=BC=88N3/N4/N5?=
=?UTF-8?q?=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
N3 洞察页三 tab(飞轮/健康中心/全局治理):
- FlywheelPanel(版本队列/阻塞/停转归档/Git质量/梦核)整体迁出 BlueprintModal
——跨项目内容不再寄生单项目弹窗,BlueprintModal 减重 414 行
- CONVENTIONS 规则来源 + 应用梦核建议迁至全局治理 tab(影响所有项目的操作归全局层)
- BlueprintModal 侧板只留单项目内容(模块详情/余票/可派发/项目治理)
N4 Gitea 层级归位:实例管理(全局配置)拆为 GiteaInstancesSection 挂设置页;
项目绑定+webhook 留治理面板,无实例时提示去设置页添加;共用 queryKey 缓存
N5 仓库注册表并入 项目→管理→仓库 tab(RegistryTab 复用),Sidebar 底部入口
与弹窗死壳移除;总览页更名对齐"跨项目行动项"定位
至此 ui-restructure N1-N5 全部完成:三条公理(名实一致/层级对齐/频率分层)落地。
零后端变更,typecheck 全绿。
---
.blueprint/manifest.yaml | 6 +-
.blueprint/modules/gitea-integration.md | 4 +-
.blueprint/modules/ui-restructure.md | 22 +-
.blueprint/usage.json | 240 +++++++
src/App.tsx | 5 +-
src/components/dashboard/BlueprintModal.tsx | 599 +-----------------
src/components/dashboard/GiteaPanel.tsx | 226 ++++---
.../dashboard/RepoRegistryModal.tsx | 28 +-
src/components/insight/FlywheelPanel.tsx | 423 +++++++++++++
.../insight/GlobalGovernancePanel.tsx | 151 +++++
src/components/insight/InsightPage.tsx | 52 ++
src/components/layout/Sidebar.tsx | 20 -
src/components/manage/ManagePage.tsx | 12 +-
src/components/overview/OverviewPage.tsx | 4 +-
src/components/settings/SettingsPage.tsx | 2 +
15 files changed, 1031 insertions(+), 763 deletions(-)
create mode 100644 src/components/insight/FlywheelPanel.tsx
create mode 100644 src/components/insight/GlobalGovernancePanel.tsx
create mode 100644 src/components/insight/InsightPage.tsx
diff --git a/.blueprint/manifest.yaml b/.blueprint/manifest.yaml
index d718c1d..13b7191 100644
--- a/.blueprint/manifest.yaml
+++ b/.blueprint/manifest.yaml
@@ -400,9 +400,9 @@ modules:
- id: ui-restructure
name: 功能区 UI 重构
area: frontend
- status: in_progress
- progress: 40
- note: "阶段一完成(N1 六组导航 + N2 卡片分层);阶段二 N3/N4/N5 待用一周后启动"
+ status: done
+ progress: 100
+ note: "N1-N5 全部完成(2026-07-02):六组导航 + 卡片分层 + 洞察页 + Gitea/仓库归位"
position: [3750, 300]
edges:
diff --git a/.blueprint/modules/gitea-integration.md b/.blueprint/modules/gitea-integration.md
index 624fbc8..d23f405 100644
--- a/.blueprint/modules/gitea-integration.md
+++ b/.blueprint/modules/gitea-integration.md
@@ -96,8 +96,8 @@ Gitea push → POST /webhook/gitea/:project_id
> ⚠️ push 事件部分已被 G3/G4 实现取代;剩余范围收窄为「PR/CI/review 事件 + 轮询降级」(见「PR 事件 ↔ 蓝图任务卡联动」concept 卡)。
-### 📋 Gitea Actions workflow 生成
-- status: todo
+### 🔵 Gitea Actions workflow 生成
+- status: in_progress
- complexity: M
- depends: G1
- files: src-tauri/src/commands/cicd.rs, src/components/dashboard/CicdModal.tsx
diff --git a/.blueprint/modules/ui-restructure.md b/.blueprint/modules/ui-restructure.md
index 37042b2..de03bf6 100644
--- a/.blueprint/modules/ui-restructure.md
+++ b/.blueprint/modules/ui-restructure.md
@@ -37,22 +37,22 @@ Gitea 全局实例配置错位在单项目弹窗内。
- files: src/components/dashboard/ProjectCard.tsx
- acceptance: 底部操作区只显示 🗺️ 蓝图 / 🎓 导师 / 编辑 / 动态 四个明按钮 + ⋯ 更多下拉(收纳 🤖 Claude 配置、📝 个人备忘、🖥️ 关联服务器、🚀 CI/CD、🔬 环境扫描、🧰 工具管理,带文字标签);下拉点击外部自动关闭;所有原功能可达;typecheck 通过
-### 📋 N3. 洞察页:飞轮跨项目内容迁出 BlueprintModal + 健康中心并入
-- status: todo
+### ✅ N3. 洞察页:飞轮跨项目内容迁出 BlueprintModal + 健康中心并入
+- status: done
- complexity: M
- depends: N1
- files: src/components/insight/InsightPage.tsx(新增), src/components/dashboard/BlueprintModal.tsx, src/components/health/HealthPage.tsx
- acceptance: 洞察页 3 tab:飞轮 / 健康 / 全局治理;FlywheelPanel 的跨项目内容(梦核提示词、归档候选、全项目 Git 质量表、全量导入按钮)迁至飞轮 tab;CONVENTIONS 版本管理与应用梦核建议迁至全局治理 tab;BlueprintModal 侧板只保留单项目内容;typecheck 通过
-### 📋 N4. Gitea 实例管理迁设置页(GiteaPanel 拆全局/项目级两半)
-- status: todo
+### ✅ N4. Gitea 实例管理迁设置页(GiteaPanel 拆全局/项目级两半)
+- status: done
- complexity: S
- depends: N1
- files: src/components/dashboard/GiteaPanel.tsx, src/components/settings/SettingsPage.tsx
- acceptance: 实例管理区(添加/测试/删除)拆为独立组件挂到设置页;BlueprintModal 治理面板只保留项目级绑定与 webhook 区(无实例时提示去设置页添加);两处共用 ["gitea-instances"] queryKey;typecheck 通过
-### 📋 N5. 仓库注册表并入项目页管理 tab + 总览收敛为行动项
-- status: todo
+### ✅ N5. 仓库注册表并入项目页管理 tab + 总览收敛为行动项
+- status: done
- complexity: S
- depends: N1
- files: src/components/manage/ManagePage.tsx, src/components/layout/Sidebar.tsx, src/components/overview/OverviewPage.tsx, src/components/dashboard/RepoRegistryModal.tsx
@@ -75,3 +75,13 @@ Gitea 全局实例配置错位在单项目弹窗内。
遗留风险: ① 洞察页暂挂健康中心(N3 未做前名实略不符,导航先行占位);② ProjectCard 更多菜单向上弹出,卡片在视口顶部时可能被裁剪(overflow 场景待实际使用观察)
auto_applied_rules: R01
rule_effectiveness: R01=未触发(无新 command,纯前端)
+
+【复盘】N3+N4+N5 阶段二:洞察页 + 层级归位(2026-07-02)
+任务类型: 前端组件
+复杂度: M + S×2
+实际轮数: 1轮(预估 1 轮)
+是否返工: 否
+有效策略: FlywheelPanel 整体函数级迁移(组件+子组件+查询原样搬家,仅去掉 onClose),BlueprintModal 用脚本按锚点批量切除 414 行避免手工 Edit 匹配失败;全局/项目级拆分以"操作影响范围"为判据(影响所有项目→洞察/设置,影响单项目→弹窗)
+遗留风险: ① RegistryTab 在 ManagePage 中的 max-h-[70vh] 容器是估计值,长列表滚动体验待实际验证;② BlueprintModal 删除跨项目内容后侧板宽度 340px 未调整,治理面板可能显空旷
+auto_applied_rules: R04
+rule_effectiveness: R04=有效(本 session 改动 10+ 文件但全部在预建任务卡范围内,无漂移)
diff --git a/.blueprint/usage.json b/.blueprint/usage.json
index a96fb76..c9cb97c 100644
--- a/.blueprint/usage.json
+++ b/.blueprint/usage.json
@@ -8318,6 +8318,246 @@
"todo": 4,
"total": 188
}
+ },
+ {
+ "at": "2026-07-02T05:41:07Z",
+ "blocked_reasons": [],
+ "conventions_version": "1.9.0",
+ "delta": {
+ "newly_blocked": [],
+ "newly_done": [
+ "agent-infrastructure/F3. Agent 健康诊断 + 可复制修正指令",
+ "flywheel-intelligence/R2. CONVENTIONS 规则边界字段 + R11 失败记录规则",
+ "gitea-integration/G2. 仓库创建 / 迁移 / 绑定",
+ "gitea-integration/G3. Webhook 接收端(MCP Server 扩展)",
+ "gitea-integration/G4. Webhook 注册命令",
+ "gitea-integration/G5. Gitea 治理面板 UI",
+ "github-decommission/D1. 免登录改造(去掉 GitHub OAuth 登录门槛)",
+ "github-decommission/D2. 移除 GitHub 发布 / 导入 / PR / CI 状态入口",
+ "github-decommission/D3. 删除 spacesSync(单机化,跨空间 GitHub 云同步下线)",
+ "github-decommission/D4. git_ops 凭据去 GitHub 化",
+ "ui-restructure/N1. 资源/工具一级页 + Sidebar 六组重组 + Dashboard 瘦身",
+ "ui-restructure/N2. ProjectCard 操作分层(4 明按钮 + ⋯ 更多下拉)"
+ ],
+ "unblocked": []
+ },
+ "iteration": 1,
+ "modules": {
+ "blocked": 0,
+ "concept": 0,
+ "done": 48,
+ "in_progress": 3,
+ "planned": 0,
+ "stalled": [
+ "flywheel-intelligence",
+ "ui-restructure"
+ ],
+ "total": 53
+ },
+ "task_ids": {
+ "blocked": [],
+ "done": [
+ "agent-infrastructure/A. 框架层扫描 + 契约路径推断",
+ "agent-infrastructure/B. 三件套文件生成",
+ "agent-infrastructure/C. 文档腐化评分",
+ "agent-infrastructure/D. 栈确认 UI + 生成按钮",
+ "agent-infrastructure/E. 看板腐化指示器",
+ "agent-infrastructure/F3. Agent 健康诊断 + 可复制修正指令",
+ "api-keys/API 密钥 CRUD 与安全展示",
+ "auto-update/Capabilities 权限配置",
+ "auto-update/下载 & 安装",
+ "auto-update/更新检测",
+ "auto-update/自动检查更新",
+ "auto-update/错误提示",
+ "blueprint-buff/BlueprintView 跨项目切换",
+ "blueprint-buff/Buff 生命周期 Rust 命令",
+ "blueprint-buff/blueprint_buffs 数据表",
+ "blueprint-buff/git watcher + 任务匹配写回 + 飞轮快照",
+ "blueprint-buff/前端 Buff 管理 UI",
+ "blueprint-buff/前端实时刷新",
+ "blueprint-feedback/归档 command",
+ "blueprint-feedback/归档候选展示",
+ "blueprint-feedback/梦核分析入口(蓝图弹窗)",
+ "blueprint-feedback/梦核提示词增加停滞模块数据",
+ "blueprint-flywheel/get_flywheel_stats command",
+ "blueprint-flywheel/get_flywheel_stats 支持迭代感知",
+ "blueprint-flywheel/usage.json 埋点写入",
+ "blueprint-flywheel/梦核提示词生成 command",
+ "blueprint-flywheel/飞轮健康面板",
+ "blueprint-governance/CLAUDE.md 模板定义",
+ "blueprint-governance/CONVENTIONS.md 版本化",
+ "blueprint-governance/GovernancePanel 规则编辑器 UI",
+ "blueprint-governance/get_blueprint_status command",
+ "blueprint-governance/sync/梦核/summary 切换为 effective 内容",
+ "blueprint-governance/sync_blueprint_rules command",
+ "blueprint-governance/批量同步所有项目规则",
+ "blueprint-governance/用户版 CONVENTIONS 存储层初始化",
+ "blueprint-governance/用户版 CONVENTIONS 读写 command",
+ "blueprint-onboarding/generate_blueprint_prompt command",
+ "blueprint-onboarding/蓝图操作弹窗",
+ "blueprint-onboarding/项目列表蓝图状态徽章",
+ "blueprint-reader/合并返回完整蓝图数据",
+ "blueprint-reader/读取 manifest.yaml",
+ "blueprint-reader/读取 modules/*.md",
+ "blueprint-view/.blueprint/ 数据结构设计",
+ "blueprint-view/React Flow 节点图渲染",
+ "blueprint-view/blocked 状态支持",
+ "blueprint-view/下一步汇总面板",
+ "blueprint-view/任务卡复制派发",
+ "blueprint-view/批量执行提示词入口",
+ "blueprint-view/智能连线路由",
+ "blueprint-view/模块详情面板",
+ "blueprint-view/统计汇总条",
+ "blueprint-view/蓝图治理面板",
+ "blueprint-view/蓝图读取 Command",
+ "blueprint-view/边高亮与模块聚焦",
+ "blueprint-view/顶部状态行扩展",
+ "blueprint-view/项目卡片蓝图入口",
+ "cicd-workflow/Actions 运行历史",
+ "cicd-workflow/CI/CD 配置 CRUD",
+ "cicd-workflow/Tauri OSS 分发模板",
+ "cicd-workflow/Web SSH 部署模板",
+ "cicd-workflow/Workflow 文件生成",
+ "claude-autonomy-config/Beast 全局状态读取与展示",
+ "cloud-db-instances/云数据库实例信息管理",
+ "cloud-docker-apps/Docker 应用列表与状态管理",
+ "cloud-products/DB 迁移:cloud_products 表",
+ "cloud-products/Dashboard 接入:云产品标签页",
+ "cloud-products/Rust CRUD:cloud_products.rs",
+ "cloud-products/前端 Panel:CloudProductsPanel.tsx",
+ "cloud-products/接线:mod.rs + lib.rs + commands.ts",
+ "deploy-info/项目部署信息记录面板",
+ "devtools-scan/WSL 工具扫描",
+ "devtools-scan/Windows 工具扫描",
+ "devtools-scan/工具信息持久化",
+ "flywheel-intelligence/F1. Git 健康度后端——全量历史导入 + 跨项目聚合",
+ "flywheel-intelligence/F2. 飞轮面板——Git 健康度区块 + 全量导入按钮",
+ "flywheel-intelligence/MCP Server 支持 SSE 传输",
+ "flywheel-intelligence/R2. CONVENTIONS 规则边界字段 + R11 失败记录规则",
+ "flywheel-intelligence/复盘笔记覆盖率可见性",
+ "flywheel-intelligence/独立项目自动创建产品组",
+ "flywheel-intelligence/补测试:飞轮存量函数安全网",
+ "flywheel-intelligence/阶段一:复盘笔记接入梦核飞轮",
+ "flywheel-intelligence/阶段二:usage.json 扩展采集维度",
+ "git-ops/上游状态检测",
+ "git-ops/分支管理",
+ "git-ops/基础操作",
+ "gitea-integration/G1. DB schema + Gitea 实例 CRUD",
+ "gitea-integration/G2. 仓库创建 / 迁移 / 绑定",
+ "gitea-integration/G3. Webhook 接收端(MCP Server 扩展)",
+ "gitea-integration/G4. Webhook 注册命令",
+ "gitea-integration/G5. Gitea 治理面板 UI",
+ "github-auth/OAuth Device Flow",
+ "github-auth/Token 管理",
+ "github-decommission/D1. 免登录改造(去掉 GitHub OAuth 登录门槛)",
+ "github-decommission/D2. 移除 GitHub 发布 / 导入 / PR / CI 状态入口",
+ "github-decommission/D3. 删除 spacesSync(单机化,跨空间 GitHub 云同步下线)",
+ "github-decommission/D4. git_ops 凭据去 GitHub 化",
+ "github-publish/GitHub 仓库创建与推送",
+ "health-center/T1 · Backend 健康检查聚合命令",
+ "health-center/T2 · 质量指标聚合",
+ "health-center/T3 · 前端健康中心页面",
+ "health-center/T4 · 扩展 MCP get_diagnostics",
+ "host-apps/宿主机应用清单与快启",
+ "mcp-config-inject/加入产品组时注入配置",
+ "mcp-config-inject/端口变更时重新注入所有配置",
+ "mcp-config-inject/退出产品组时清除配置",
+ "mcp-server/MCP 端口可配置(Rust 侧)",
+ "mcp-server/启动时绑定 HTTP 端口",
+ "mcp-server/实现 MCP JSON-RPC 基础协议",
+ "mcp-server/实现产品组上下文工具",
+ "onboarding-pack/A 模板分发引擎",
+ "onboarding-pack/B apply_onboarding_pack 编排 + 前端入口",
+ "onboarding-pack/C1 commit_metrics 表 + Conventional 解析器",
+ "onboarding-pack/C2 挂接 buff watcher + 飞轮快照",
+ "onboarding-pack/D 数据可信度提示",
+ "product-group-mcp/产品组详情显示 MCP 注入状态",
+ "product-group-mcp/设置页添加 MCP 端口配置",
+ "project-dashboard/分组画布",
+ "project-dashboard/项目卡片列表",
+ "project-dashboard/项目发现面板",
+ "project-dashboard/项目快捷操作",
+ "project-env-scan/环境信息持久化",
+ "project-env-scan/项目环境检测",
+ "project-mentor/T1 · 后端聚合 command(MentorContext)",
+ "project-mentor/T10 · 启动健康扫描 + 前端 Alert Banner",
+ "project-mentor/T11 · 项目历史快照",
+ "project-mentor/T2 · MCP 项目导师查询接口",
+ "project-mentor/T3 · 前端导师面板",
+ "project-mentor/T4 · 炼境自指接入",
+ "project-mentor/T5 · project_notes 数据层 + 后端命令",
+ "project-mentor/T6 · MCP append_project_note 工具",
+ "project-mentor/T7 · MentorPanel 展示 AI 笔记",
+ "project-mentor/T8 · 后端全组巡检命令",
+ "project-mentor/T9 · 前端全组巡检按钮",
+ "project-mgmt/子文件夹扫描",
+ "project-mgmt/批量导入项目",
+ "project-mgmt/项目 CRUD",
+ "project-mgmt/项目档案(Profile)",
+ "project-templates/P1+P3 模板扩充(React 生态全覆盖)",
+ "project-templates/从模板创建项目",
+ "project-templates/内置模板种植",
+ "project-templates/模板 CRUD",
+ "project-tools/工具 CRUD",
+ "project-tools/工具启动",
+ "repo-registry/仓库导入",
+ "repo-registry/仓库挂载",
+ "repo-registry/仓库注册表管理",
+ "runtime-diagnostics/T1 · SQLite runtime_logs 表",
+ "runtime-diagnostics/T2 · 替换 eprintln! 为结构化日志",
+ "runtime-diagnostics/T3 · MCP Server 新增 get_diagnostics 工具",
+ "runtime-diagnostics/T4 · 注入失败结果返回前端",
+ "runtime-diagnostics/T5 · 设置页 MCP 真实健康检查",
+ "server-registry/服务器 CRUD 与项目关联",
+ "server-software/服务器软件扫描与展示",
+ "service-config/服务配置 CRUD 与环境分组",
+ "settings-page/WSL 发行版选择",
+ "settings-page/关于 & 更新",
+ "settings-page/终端配置",
+ "settings-page/编辑器检测",
+ "shell-launch/打开终端",
+ "shell-launch/打开编辑器",
+ "source-library/源码库分类管理与项目详情",
+ "sqlite-db/数据库迁移",
+ "sqlite-db/连接池初始化",
+ "ssh-config-sync/SSH Config 写入与同步",
+ "tags-groups/分组管理",
+ "tags-groups/标签管理",
+ "tray-service/关闭窗口最小化到托盘",
+ "tray-service/系统托盘图标与菜单",
+ "tray-service/集成 tauri-plugin-single-instance",
+ "ui-restructure/N1. 资源/工具一级页 + Sidebar 六组重组 + Dashboard 瘦身",
+ "ui-restructure/N2. ProjectCard 操作分层(4 明按钮 + ⋯ 更多下拉)",
+ "user-project-todos/前端:备忘清单 UI 组件",
+ "user-project-todos/数据层:user_project_todos 表 + Rust commands",
+ "v2rayn-import/当前节点读取",
+ "v2rayn-import/数据库路径检测",
+ "v2rayn-import/节点列表扫描",
+ "vscode-remote/VS Code Remote SSH 一键连接",
+ "workspace-mgmt/空间 CRUD",
+ "workspace-mgmt/空间级设置",
+ "workspace-mgmt/部署命令管理",
+ "wsl-proxy/代理一致性测试",
+ "wsl-proxy/代理同步",
+ "wsl-proxy/代理清除",
+ "wsl-proxy/代理状态检测"
+ ]
+ },
+ "tasks": {
+ "avg_rounds_M": null,
+ "blocked": 0,
+ "complexity_dist": {
+ "L": 6,
+ "M": 80,
+ "S": 53
+ },
+ "dispatched": 0,
+ "done": 184,
+ "in_progress": 0,
+ "rework_count": 23,
+ "todo": 7,
+ "total": 201
+ }
}
]
}
\ No newline at end of file
diff --git a/src/App.tsx b/src/App.tsx
index 261ecd2..2dc2583 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -5,7 +5,7 @@ import { ProjectModal } from "./components/dashboard/ProjectModal";
import { ResourcesPage } from "./components/resources/ResourcesPage";
import { ToolsPage } from "./components/tools/ToolsPage";
import { SettingsPage } from "./components/settings/SettingsPage";
-import { HealthPage } from "./components/health/HealthPage";
+import { InsightPage } from "./components/insight/InsightPage";
import { Sidebar, type Page } from "./components/layout/Sidebar";
import { Toast } from "./components/ui/Toast";
import { getStartupAlerts, type StartupAlert } from "./lib/commands";
@@ -68,8 +68,7 @@ export default function App() {
{page === "overview" && }
{page === "dashboard" && }
{page === "resources" && }
- {/* 洞察页阶段一暂挂健康中心,N3 扩充为 飞轮/健康/全局治理 三 tab */}
- {page === "insight" && }
+ {page === "insight" && }
{page === "tools" && }
{page === "settings" && }
diff --git a/src/components/dashboard/BlueprintModal.tsx b/src/components/dashboard/BlueprintModal.tsx
index c5db0f5..94e767f 100644
--- a/src/components/dashboard/BlueprintModal.tsx
+++ b/src/components/dashboard/BlueprintModal.tsx
@@ -20,33 +20,19 @@ import {
getBlueprintStatus,
syncBlueprintRules,
generateBlueprintPrompt,
- getProjects,
- getFlywheelStats,
- generateMuhePrompt,
- archiveProjectDocs,
applyOnboardingPack,
getCommitStats,
removeBlueprintBuff,
getBuffStatus,
- getUserConventionsStatus,
- saveUserConventions,
- resetConventionsToBuiltin,
type BlueprintData,
type BlueprintModule,
type BlueprintTask,
type BlueprintArea,
type BlueprintStatus,
type SyncResult,
- type Project,
- type FlywheelStats,
- type StalledProject,
type BuffStatus,
type TemplateResult,
type CommitStats,
- type UserConventionsStatus,
- type CommitHealthWithName,
- ingestFullGitHistory,
- getAllCommitHealth,
} from "../../lib/commands";
interface Props {
@@ -253,7 +239,7 @@ function pickHandles(
// ── 主组件 ───────────────────────────────────────────────────────────────────
-type SidePanel = { type: "module"; module: BlueprintModule } | { type: "next" } | { type: "remaining" } | { type: "governance" } | { type: "flywheel" } | null;
+type SidePanel = { type: "module"; module: BlueprintModule } | { type: "next" } | { type: "remaining" } | { type: "governance" } | null;
export function BlueprintModal({ projectName, projectPath, projectId, onClose }: Props) {
const [sidePanel, setSidePanel] = useState(null);
@@ -429,16 +415,6 @@ export function BlueprintModal({ projectName, projectPath, projectId, onClose }:
-
)}
-
{/* 总进度条(固定在右侧) */}
{pinnedModuleId && (
@@ -639,10 +604,7 @@ export function BlueprintModal({ projectName, projectPath, projectId, onClose }:
projectName={projectName}
projectId={projectId}
onClose={() => setSidePanel(null)}
- onOpenFlywheel={() => setSidePanel({ type: "flywheel" })}
/>
- ) : sidePanel.type === "flywheel" ? (
-
setSidePanel(null)} />
) : (
void;
- onOpenFlywheel?: () => void;
}) {
const { data: bpStatus, isPending: statusLoading, refetch: refetchStatus } = useQuery({
queryKey: ["bp-status-governance", projectPath],
@@ -1092,17 +1052,6 @@ function GovernancePanel({
const [promptError, setPromptError] = useState(null);
const [copied, setCopied] = useState(false);
- // 用户版 CONVENTIONS 编辑器
- const { data: userConventions, refetch: refetchUserConventions } = useQuery({
- queryKey: ["user-conventions-status"],
- queryFn: () => getUserConventionsStatus(),
- });
- const [conventionsInput, setConventionsInput] = useState("");
- const [conventionsEditorOpen, setConventionsEditorOpen] = useState(false);
- const [conventionsSaving, setConventionsSaving] = useState(false);
- const [conventionsSaveResult, setConventionsSaveResult] = useState(null);
- const [conventionsSaveError, setConventionsSaveError] = useState(null);
-
const handleApplyBuff = async () => {
if (!projectId) return;
setBuffLoading(true);
@@ -1189,41 +1138,6 @@ function GovernancePanel({
});
};
- const handleSaveConventions = async () => {
- if (!conventionsInput.trim()) return;
- setConventionsSaving(true);
- setConventionsSaveError(null);
- setConventionsSaveResult(null);
- try {
- const res = await saveUserConventions(conventionsInput);
- setConventionsSaveResult(`已应用 v${res.version},同步了 ${res.synced_count} 个项目`);
- setConventionsInput("");
- setConventionsEditorOpen(false);
- refetchUserConventions();
- refetchStatus();
- } catch (e) {
- setConventionsSaveError(String(e));
- } finally {
- setConventionsSaving(false);
- }
- };
-
- const handleResetConventions = async () => {
- setConventionsSaving(true);
- setConventionsSaveError(null);
- setConventionsSaveResult(null);
- try {
- const res = await resetConventionsToBuiltin();
- setConventionsSaveResult(`已重置为内置基线 v${res.version},同步了 ${res.synced_count} 个项目`);
- refetchUserConventions();
- refetchStatus();
- } catch (e) {
- setConventionsSaveError(String(e));
- } finally {
- setConventionsSaving(false);
- }
- };
-
const rulesOutdated = bpStatus?.status === "rules_outdated";
const currentVersion = bpStatus?.conventions_version ?? "—";
const masterVersion = bpStatus?.master_version ?? "—";
@@ -1265,88 +1179,6 @@ function GovernancePanel({
)}
- {/* 规则来源 badge */}
- {userConventions && (
-
- 规则来源
- {userConventions.source === "user" ? (
-
- 用户自定义 v{userConventions.version}
- {userConventions.outdated_from_builtin && (
- ⚠️ 低于内置
- )}
-
- ) : (
-
- 内置基线 v{userConventions.builtin_version}
-
- )}
-
- )}
-
- {/* 应用梦核建议 */}
-
-
-
应用梦核建议
-
-
- {conventionsEditorOpen && (
-
-
- 将梦核 Opus 输出的新 CONVENTIONS 内容粘贴到下方,确认后将写入并同步所有项目。
-
-
- )}
- {!conventionsEditorOpen && userConventions?.source === "user" && (
-
-
当前使用用户自定义规则
-
-
- )}
- {conventionsSaveResult && (
-
✓ {conventionsSaveResult}
- )}
-
-
{/* 同步规则区域 */}
{rulesOutdated && !syncResult && (
@@ -1437,21 +1269,6 @@ function GovernancePanel({
)}
{/* 梦核分析入口 */}
- {onOpenFlywheel && (
-
-
梦核 · 记忆巩固
-
- 基于跨项目飞轮数据,生成梦核提示词,由 Claude Opus 完成固化 / 遗忘判断。
-
-
-
- )}
-
{/* ── 蓝图 Buff ─────────────────────────────────────── */}
@@ -1636,417 +1453,3 @@ function RemainingTasksPanel({
);
}
-// ── 飞轮健康面板 ────────────────────────────────────────────────────────────
-
-function StalledProjectRow({ p, projectsMap, onArchive }: {
- p: StalledProject;
- projectsMap: Record
; // name → path
- onArchive?: (path: string, name: string) => void;
-}) {
- const path = projectsMap[p.name];
- return (
-
-
-
· {p.name}
-
最后活跃: {p.last_active}
-
- {onArchive && path && (
-
- )}
-
- );
-}
-
-function FlywheelPanel({ onClose }: { onClose: () => void }) {
- const { data: projects } = useQuery({
- queryKey: ["projects-for-flywheel"],
- queryFn: () => getProjects(),
- });
-
- const { projectPaths, projectsMap, projectsWithPath } = useMemo(() => {
- const paths = (projects ?? [])
- .map((p) => p.win_path || p.wsl_path || "")
- .filter(Boolean);
- const map: Record = {};
- const withPath: Array<{ id: string; name: string; path: string }> = [];
- for (const p of (projects ?? [])) {
- const path = p.win_path || p.wsl_path || "";
- if (!path) continue;
- withPath.push({ id: p.id, name: p.name, path });
- map[p.name] = path;
- const dirName = path.replace(/\\/g, "/").split("/").filter(Boolean).pop() ?? "";
- if (dirName && !map[dirName]) map[dirName] = path;
- }
- return { projectPaths: paths, projectsMap: map, projectsWithPath: withPath };
- }, [projects]);
-
- const { data: commitHealth, refetch: refetchHealth } = useQuery({
- queryKey: ["commit-health-all"],
- queryFn: () => getAllCommitHealth(),
- });
-
- const [ingesting, setIngesting] = useState(false);
- const [ingestSummary, setIngestSummary] = useState(null);
-
- const { data: stats, isLoading } = useQuery({
- queryKey: ["flywheel-stats", projectPaths],
- queryFn: () => getFlywheelStats(projectPaths),
- enabled: projectPaths.length > 0,
- });
-
- const [muhePrompt, setMuhePrompt] = useState("");
- const [generating, setGenerating] = useState(false);
- const [copied, setCopied] = useState(false);
- const [archiveMsg, setArchiveMsg] = useState(null);
- const [archiving, setArchiving] = useState(null);
- const [pendingArchive, setPendingArchive] = useState<{ path: string; name: string } | null>(null);
-
- const handleIngestAll = async () => {
- setIngesting(true);
- setIngestSummary(null);
- let totalImported = 0;
- let totalSkipped = 0;
- for (const { id, path } of projectsWithPath) {
- try {
- const r = await ingestFullGitHistory(id, path);
- totalImported += r.imported;
- totalSkipped += r.skipped;
- } catch {
- // 单个项目失败不中断其他项目
- }
- }
- await refetchHealth();
- setIngestSummary(`新导入 ${totalImported} 条,跳过 ${totalSkipped} 条`);
- setIngesting(false);
- };
-
- const handleGenerateMuhe = async () => {
- if (!stats) return;
- setGenerating(true);
- try {
- const result = await generateMuhePrompt(stats);
- setMuhePrompt(result);
- } finally {
- setGenerating(false);
- }
- };
-
- const handleCopy = () => {
- navigator.clipboard.writeText(muhePrompt).then(() => {
- setCopied(true);
- setTimeout(() => setCopied(false), 2000);
- });
- };
-
- const handleArchiveConfirm = async () => {
- if (!pendingArchive) return;
- const { path, name } = pendingArchive;
- setPendingArchive(null);
- setArchiving(name);
- setArchiveMsg(null);
- try {
- const result = await archiveProjectDocs(path);
- setArchiveMsg(`✓ 已归档 ${result.archived_files.length} 个文件 → ${result.archive_dir}`);
- } catch (e) {
- setArchiveMsg(`归档失败: ${String(e)}`);
- } finally {
- setArchiving(null);
- }
- };
-
- const fmtPct = (v: number) => `${(v * 100).toFixed(1)}%`;
-
- // Fix 2:去重——90天休眠的项目不在60天停转里重复展示
- const stalledOnlyProjects = (stats?.stalled_projects ?? []).filter(
- (p) => !(stats?.ai_doc_stale_projects ?? []).some((q) => q.name === p.name)
- );
-
- return (
-
-
-
-
飞轮健康
- {stats && (
-
- {stats.projects_with_data}/{stats.total_projects_scanned} 个项目有数据
-
- )}
-
-
-
-
- {isLoading ? (
-
聚合中…
- ) : !stats || stats.projects_with_data === 0 ? (
-
-
暂无飞轮数据
-
打开各项目蓝图后数据将自动积累
-
- ) : (
- <>
- {/* CONVENTIONS 版本队列对比 */}
- {stats.cohorts.length > 0 && (
-
-
版本队列对比
-
- {stats.cohorts.map((c) => (
-
-
- v{c.conventions_version}
- {c.project_count} 个项目
-
- {/* Fix 4:标签改为"任务完成率",字段改为 completion_rate */}
-
- 0.3} />
- 0.5} />
- 0.6} />
-
-
-
- ))}
-
-
- )}
-
- {/* TOP blocked reasons */}
- {stats.top_blocked_reasons.length > 0 && (
-
-
TOP 阻塞原因
-
- {stats.top_blocked_reasons.map((r, i) => (
- -
- #{i + 1}
- {r.reason}
- ×{r.count}
-
- ))}
-
-
- )}
-
- {/* Fix 2:仅展示"停转但未到90天"的项目 */}
- {stalledOnlyProjects.length > 0 && (
-
-
- 飞轮停转(60–90天无快照)
-
- {stalledOnlyProjects.map((p, i) => (
-
- ))}
-
- )}
-
- {/* AI文档休眠项目(≥90天) */}
- {stats.ai_doc_stale_projects.length > 0 && (
-
-
- AI文档休眠(90天+无快照)
-
-
- 建议归档 .blueprint/modules/,清空 Claude 上下文负担。
-
- {/* Fix 1:归档确认弹窗 */}
- {pendingArchive ? (
-
-
- 确认归档「{pendingArchive.name}」?
-
-
- 将删除 .blueprint/modules/*.md,文件会备份到 .blueprint/archive/,但操作本身不可一键撤销。
-
-
-
-
-
-
- ) : (
- stats.ai_doc_stale_projects.map((p, i) => (
-
setPendingArchive({ path, name })}
- />
- ))
- )}
- {archiving && (
- 归档 {archiving} 中…
- )}
- {archiveMsg && (
-
- {archiveMsg}
-
- )}
-
- )}
-
- {/* 迭代跳变项目 */}
- {(stats.iteration_jumps ?? []).length > 0 && (
-
-
- 迭代跳变(主动重构,完成率重置)
-
- {(stats.iteration_jumps ?? []).map((j, i) => (
-
- {j.project}
- 迭代 {j.from_iteration}→{j.to_iteration}
-
- {(j.rate_before * 100).toFixed(0)}%→{(j.rate_after * 100).toFixed(0)}%
- {j.rate_after < j.rate_before - 0.3 && " ⚠️"}
-
-
- ))}
-
- )}
-
- {/* 停滞模块 */}
- {(stats.stalled_modules ?? []).length > 0 && (
-
-
- 停滞模块(in_progress 但无可推进任务)
-
-
- {(stats.stalled_modules ?? []).map((m, i) => (
- - {m}
- ))}
-
-
- )}
-
- {/* Git 执行质量 */}
-
-
-
Git 执行质量
-
-
- {ingestSummary && (
-
{ingestSummary}
- )}
- {commitHealth && commitHealth.length > 0 ? (
-
-
-
-
- | 项目 |
- commits |
- 规范化 |
- 返工率 |
- TOP scope |
-
-
-
- {commitHealth.map((h) => {
- return (
-
- |
- {h.project_name}
- |
- {h.total_commits} |
- = 0.8 ? "text-green-600" : h.conventional_rate >= 0.5 ? "text-amber-500" : "text-red-500"}`}>
- {Math.round(h.conventional_rate * 100)}%
- |
- 0.2 ? "text-red-500" : h.rework_rate > 0.1 ? "text-amber-500" : "text-gray-500"}`}>
- {Math.round(h.rework_rate * 100)}%
- |
-
- {h.top_scopes.slice(0, 2).map((s) => `${s.scope}(${s.commit_count})`).join(" ")}
- |
-
- );
- })}
-
-
-
- ) : (
-
- 暂无数据——点"全量导入"从各项目 git 历史初始化
-
- )}
-
-
- {/* 梦核分析 */}
-
-
梦核分析
-
- 生成包含以上数据的提示词,交给 Claude Opus 做固化/遗忘判断。
-
- {!muhePrompt ? (
-
- ) : (
-
- )}
-
- >
- )}
-
- );
-}
-
-function MetricRow({ label, value, good, invert = false }: {
- label: string;
- value: string;
- good: boolean;
- invert?: boolean;
-}) {
- const isGood = invert ? !good : good;
- return (
-
- {label}
-
- {value}
-
-
- );
-}
diff --git a/src/components/dashboard/GiteaPanel.tsx b/src/components/dashboard/GiteaPanel.tsx
index 4665ae1..824b4cd 100644
--- a/src/components/dashboard/GiteaPanel.tsx
+++ b/src/components/dashboard/GiteaPanel.tsx
@@ -1,4 +1,6 @@
-// Gitea 集成治理区块:实例管理 + 当前项目仓库绑定 + webhook 注册
+// Gitea 集成,拆两半(ui-restructure N4):
+// - GiteaInstancesSection:实例管理(全局配置)→ 挂设置页
+// - GiteaPanel:当前项目仓库绑定 + webhook(项目级)→ 留项目蓝图治理面板
// 控制面在 gitea.rs,观测面(webhook 接收)在 mcp_server.rs
import { useState } from "react";
import { useQuery, useQueryClient } from "@tanstack/react-query";
@@ -18,11 +20,6 @@ import {
} from "../../lib/commands";
import { useUIStore } from "../../store/ui";
-interface Props {
- projectId: string;
- projectName: string;
-}
-
type BindMode = "create" | "migrate" | "link";
const CALLBACK_BASE_KEY = "gitea_callback_base";
@@ -32,6 +29,121 @@ const inputCls =
const btnCls =
"px-2 py-1 rounded text-[11px] border border-gray-200 text-gray-500 hover:bg-gray-50 transition-colors";
+// ── 实例管理(全局,挂设置页)────────────────────────────────────────────────
+
+export function GiteaInstancesSection() {
+ const qc = useQueryClient();
+ const showToast = useUIStore((s) => s.showToast);
+
+ const { data: instances = [] } = useQuery({
+ queryKey: ["gitea-instances"],
+ queryFn: giteaListInstances,
+ });
+
+ const [showAdd, setShowAdd] = useState(false);
+ const [instName, setInstName] = useState("");
+ const [instUrl, setInstUrl] = useState("");
+ const [instToken, setInstToken] = useState("");
+ const [testing, setTesting] = useState(null);
+
+ const handleAdd = async () => {
+ if (!instName.trim() || !instUrl.trim() || !instToken.trim()) return;
+ try {
+ await giteaSaveInstance({
+ name: instName.trim(),
+ url: instUrl.trim(),
+ access_token: instToken.trim(),
+ is_default: instances.length === 0,
+ });
+ qc.invalidateQueries({ queryKey: ["gitea-instances"] });
+ setInstName(""); setInstUrl(""); setInstToken("");
+ setShowAdd(false);
+ showToast("Gitea 实例已添加 ✓");
+ } catch (e) {
+ showToast(`❌ ${e}`);
+ }
+ };
+
+ const handleTest = async (inst: GiteaInstance) => {
+ setTesting(inst.id);
+ try {
+ const user = await giteaTestConnection(inst.id);
+ showToast(`✓ 连接成功,账户: ${user.login}`);
+ } catch (e) {
+ showToast(`❌ ${e}`);
+ } finally {
+ setTesting(null);
+ }
+ };
+
+ const handleDelete = async (inst: GiteaInstance) => {
+ if (!confirm(`删除实例「${inst.name}」?其下所有项目绑定会一并删除。`)) return;
+ try {
+ await giteaDeleteInstance(inst.id);
+ qc.invalidateQueries({ queryKey: ["gitea-instances"] });
+ qc.invalidateQueries({ queryKey: ["gitea-linked-repos"] });
+ showToast("已删除");
+ } catch (e) {
+ showToast(`❌ ${e}`);
+ }
+ };
+
+ return (
+
+
+
🪁 Gitea 实例
+ {!showAdd && (
+
+ )}
+
+
+
+ {instances.length === 0 && !showAdd && (
+
尚未添加 Gitea 实例——添加后可在各项目蓝图的治理面板中绑定仓库
+ )}
+ {instances.map((inst) => (
+
+ {inst.name}
+ {inst.is_default && (
+ 默认
+ )}
+ {inst.url}
+
+
+
+ ))}
+ {showAdd && (
+
+ )}
+
+
+ );
+}
+
+// ── 项目级绑定 + webhook(留项目蓝图治理面板)────────────────────────────────
+
+interface Props {
+ projectId: string;
+ projectName: string;
+}
+
export function GiteaPanel({ projectId, projectName }: Props) {
const qc = useQueryClient();
const showToast = useUIStore((s) => s.showToast);
@@ -56,56 +168,7 @@ export function GiteaPanel({ projectId, projectName }: Props) {
qc.invalidateQueries({ queryKey: ["gitea-linked-repos"] });
};
- // ── 实例管理 ──
- const [showAddInstance, setShowAddInstance] = useState(false);
- const [instName, setInstName] = useState("");
- const [instUrl, setInstUrl] = useState("");
- const [instToken, setInstToken] = useState("");
- const [testing, setTesting] = useState(null);
-
- const handleAddInstance = async () => {
- if (!instName.trim() || !instUrl.trim() || !instToken.trim()) return;
- try {
- await giteaSaveInstance({
- name: instName.trim(),
- url: instUrl.trim(),
- access_token: instToken.trim(),
- is_default: instances.length === 0,
- });
- qc.invalidateQueries({ queryKey: ["gitea-instances"] });
- setInstName(""); setInstUrl(""); setInstToken("");
- setShowAddInstance(false);
- showToast("Gitea 实例已添加 ✓");
- } catch (e) {
- showToast(`❌ ${e}`);
- }
- };
-
- const handleTest = async (inst: GiteaInstance) => {
- setTesting(inst.id);
- try {
- const user = await giteaTestConnection(inst.id);
- showToast(`✓ 连接成功,账户: ${user.login}`);
- } catch (e) {
- showToast(`❌ ${e}`);
- } finally {
- setTesting(null);
- }
- };
-
- const handleDeleteInstance = async (inst: GiteaInstance) => {
- if (!confirm(`删除实例「${inst.name}」?其下所有项目绑定会一并删除。`)) return;
- try {
- await giteaDeleteInstance(inst.id);
- qc.invalidateQueries({ queryKey: ["gitea-instances"] });
- refresh();
- showToast("已删除");
- } catch (e) {
- showToast(`❌ ${e}`);
- }
- };
-
- // ── 当前项目绑定 ──
+ // ── 绑定操作 ──
const [bindMode, setBindMode] = useState(null);
const [bindInstanceId, setBindInstanceId] = useState("");
const [repoName, setRepoName] = useState("");
@@ -184,51 +247,11 @@ export function GiteaPanel({ projectId, projectName }: Props) {
return (
- 🪁 Gitea 集成
+ 🪁 Gitea 绑定
- {/* ── 实例列表 ── */}
-
- {instances.length === 0 && !showAddInstance && (
-
尚未添加 Gitea 实例
- )}
- {instances.map((inst) => (
-
- {inst.name}
- {inst.is_default && (
- 默认
- )}
- {inst.url}
-
-
-
- ))}
- {showAddInstance ? (
-
- ) : (
-
- )}
-
-
{/* ── 当前项目绑定 ── */}
-
+
{link ? (
<>
@@ -280,6 +303,10 @@ export function GiteaPanel({ projectId, projectName }: Props) {
>
+ ) : instances.length === 0 ? (
+
+ 尚未配置 Gitea 实例——请先在「设置 → Gitea 实例」中添加
+
) : (
<>
@@ -328,9 +355,6 @@ export function GiteaPanel({ projectId, projectName }: Props) {
>
{binding ? "执行中…" : "确认"}
- {instances.length === 0 && (
-
请先添加 Gitea 实例
- )}
)}
>
diff --git a/src/components/dashboard/RepoRegistryModal.tsx b/src/components/dashboard/RepoRegistryModal.tsx
index 5f79c21..7b21733 100644
--- a/src/components/dashboard/RepoRegistryModal.tsx
+++ b/src/components/dashboard/RepoRegistryModal.tsx
@@ -9,10 +9,6 @@ import {
} from "../../lib/commands";
import { useUIStore } from "../../store/ui";
-interface Props {
- onClose: () => void;
-}
-
// ── 部署信息编辑表单 ──────────────────────────────────────────────────────────
function DeployForm({
@@ -118,8 +114,9 @@ function DeployForm({
}
// ── 已注册仓库列表 ────────────────────────────────────────────────────────────
+// 同时被 ManagePage「仓库」tab 复用(ui-restructure N5)
-function RegistryTab() {
+export function RegistryTab() {
const showToast = useUIStore((s) => s.showToast);
const qc = useQueryClient();
const [expandedId, setExpandedId] = useState
(null);
@@ -149,7 +146,7 @@ function RegistryTab() {
return (
📦
-
还没有注册仓库,通过「发布 / 绑定仓库」流程注册
+
还没有注册仓库——在项目编辑中填写仓库地址或从仓库挂载后自动注册
);
}
@@ -214,22 +211,3 @@ function RegistryTab() {
);
}
-
-// ── 主模态框 ──────────────────────────────────────────────────────────────────
-
-export function RepoRegistryModal({ onClose }: Props) {
- return (
-
- );
-}
diff --git a/src/components/insight/FlywheelPanel.tsx b/src/components/insight/FlywheelPanel.tsx
new file mode 100644
index 0000000..c717577
--- /dev/null
+++ b/src/components/insight/FlywheelPanel.tsx
@@ -0,0 +1,423 @@
+// 飞轮健康面板(跨项目):版本队列对比 / 阻塞原因 / 停转与休眠归档 / Git 执行质量 / 梦核分析
+// 2026-07-02 ui-restructure N3:从 BlueprintModal 侧板迁出为洞察页一级内容(跨项目内容不属于单项目弹窗)
+import { useState, useMemo } from "react";
+import { useQuery } from "@tanstack/react-query";
+import {
+ getProjects,
+ getFlywheelStats,
+ generateMuhePrompt,
+ archiveProjectDocs,
+ ingestFullGitHistory,
+ getAllCommitHealth,
+ type Project,
+ type FlywheelStats,
+ type StalledProject,
+ type CommitHealthWithName,
+} from "../../lib/commands";
+
+function StalledProjectRow({ p, projectsMap, onArchive }: {
+ p: StalledProject;
+ projectsMap: Record
; // name → path
+ onArchive?: (path: string, name: string) => void;
+}) {
+ const path = projectsMap[p.name];
+ return (
+
+
+
· {p.name}
+
最后活跃: {p.last_active}
+
+ {onArchive && path && (
+
+ )}
+
+ );
+}
+
+function MetricRow({ label, value, good, invert = false }: {
+ label: string;
+ value: string;
+ good: boolean;
+ invert?: boolean;
+}) {
+ const isGood = invert ? !good : good;
+ return (
+
+ {label}
+
+ {value}
+
+
+ );
+}
+
+export function FlywheelPanel() {
+ const { data: projects } = useQuery({
+ queryKey: ["projects-for-flywheel"],
+ queryFn: () => getProjects(),
+ });
+
+ const { projectPaths, projectsMap, projectsWithPath } = useMemo(() => {
+ const paths = (projects ?? [])
+ .map((p) => p.win_path || p.wsl_path || "")
+ .filter(Boolean);
+ const map: Record = {};
+ const withPath: Array<{ id: string; name: string; path: string }> = [];
+ for (const p of (projects ?? [])) {
+ const path = p.win_path || p.wsl_path || "";
+ if (!path) continue;
+ withPath.push({ id: p.id, name: p.name, path });
+ map[p.name] = path;
+ const dirName = path.replace(/\\/g, "/").split("/").filter(Boolean).pop() ?? "";
+ if (dirName && !map[dirName]) map[dirName] = path;
+ }
+ return { projectPaths: paths, projectsMap: map, projectsWithPath: withPath };
+ }, [projects]);
+
+ const { data: commitHealth, refetch: refetchHealth } = useQuery({
+ queryKey: ["commit-health-all"],
+ queryFn: () => getAllCommitHealth(),
+ });
+
+ const [ingesting, setIngesting] = useState(false);
+ const [ingestSummary, setIngestSummary] = useState(null);
+
+ const { data: stats, isLoading } = useQuery({
+ queryKey: ["flywheel-stats", projectPaths],
+ queryFn: () => getFlywheelStats(projectPaths),
+ enabled: projectPaths.length > 0,
+ });
+
+ const [muhePrompt, setMuhePrompt] = useState("");
+ const [generating, setGenerating] = useState(false);
+ const [copied, setCopied] = useState(false);
+ const [archiveMsg, setArchiveMsg] = useState(null);
+ const [archiving, setArchiving] = useState(null);
+ const [pendingArchive, setPendingArchive] = useState<{ path: string; name: string } | null>(null);
+
+ const handleIngestAll = async () => {
+ setIngesting(true);
+ setIngestSummary(null);
+ let totalImported = 0;
+ let totalSkipped = 0;
+ for (const { id, path } of projectsWithPath) {
+ try {
+ const r = await ingestFullGitHistory(id, path);
+ totalImported += r.imported;
+ totalSkipped += r.skipped;
+ } catch {
+ // 单个项目失败不中断其他项目
+ }
+ }
+ await refetchHealth();
+ setIngestSummary(`新导入 ${totalImported} 条,跳过 ${totalSkipped} 条`);
+ setIngesting(false);
+ };
+
+ const handleGenerateMuhe = async () => {
+ if (!stats) return;
+ setGenerating(true);
+ try {
+ const result = await generateMuhePrompt(stats);
+ setMuhePrompt(result);
+ } finally {
+ setGenerating(false);
+ }
+ };
+
+ const handleCopy = () => {
+ navigator.clipboard.writeText(muhePrompt).then(() => {
+ setCopied(true);
+ setTimeout(() => setCopied(false), 2000);
+ });
+ };
+
+ const handleArchiveConfirm = async () => {
+ if (!pendingArchive) return;
+ const { path, name } = pendingArchive;
+ setPendingArchive(null);
+ setArchiving(name);
+ setArchiveMsg(null);
+ try {
+ const result = await archiveProjectDocs(path);
+ setArchiveMsg(`✓ 已归档 ${result.archived_files.length} 个文件 → ${result.archive_dir}`);
+ } catch (e) {
+ setArchiveMsg(`归档失败: ${String(e)}`);
+ } finally {
+ setArchiving(null);
+ }
+ };
+
+ const fmtPct = (v: number) => `${(v * 100).toFixed(1)}%`;
+
+ // 去重——90天休眠的项目不在60天停转里重复展示
+ const stalledOnlyProjects = (stats?.stalled_projects ?? []).filter(
+ (p) => !(stats?.ai_doc_stale_projects ?? []).some((q) => q.name === p.name)
+ );
+
+ return (
+
+
+
飞轮健康
+ {stats && (
+
+ {stats.projects_with_data}/{stats.total_projects_scanned} 个项目有数据
+
+ )}
+
+
+ {isLoading ? (
+
聚合中…
+ ) : !stats || stats.projects_with_data === 0 ? (
+
+
暂无飞轮数据
+
打开各项目蓝图后数据将自动积累
+
+ ) : (
+ <>
+ {/* CONVENTIONS 版本队列对比 */}
+ {stats.cohorts.length > 0 && (
+
+
版本队列对比
+
+ {stats.cohorts.map((c) => (
+
+
+ v{c.conventions_version}
+ {c.project_count} 个项目
+
+
+ 0.3} />
+ 0.5} />
+ 0.6} />
+
+
+
+ ))}
+
+
+ )}
+
+ {/* TOP blocked reasons */}
+ {stats.top_blocked_reasons.length > 0 && (
+
+
TOP 阻塞原因
+
+ {stats.top_blocked_reasons.map((r, i) => (
+ -
+ #{i + 1}
+ {r.reason}
+ ×{r.count}
+
+ ))}
+
+
+ )}
+
+ {/* 仅展示"停转但未到90天"的项目 */}
+ {stalledOnlyProjects.length > 0 && (
+
+
+ 飞轮停转(60–90天无快照)
+
+ {stalledOnlyProjects.map((p, i) => (
+
+ ))}
+
+ )}
+
+ {/* AI文档休眠项目(≥90天) */}
+ {stats.ai_doc_stale_projects.length > 0 && (
+
+
+ AI文档休眠(90天+无快照)
+
+
+ 建议归档 .blueprint/modules/,清空 Claude 上下文负担。
+
+ {pendingArchive ? (
+
+
+ 确认归档「{pendingArchive.name}」?
+
+
+ 将删除 .blueprint/modules/*.md,文件会备份到 .blueprint/archive/,但操作本身不可一键撤销。
+
+
+
+
+
+
+ ) : (
+ stats.ai_doc_stale_projects.map((p, i) => (
+
setPendingArchive({ path, name })}
+ />
+ ))
+ )}
+ {archiving && (
+ 归档 {archiving} 中…
+ )}
+ {archiveMsg && (
+
+ {archiveMsg}
+
+ )}
+
+ )}
+
+ {/* 迭代跳变项目 */}
+ {(stats.iteration_jumps ?? []).length > 0 && (
+
+
+ 迭代跳变(主动重构,完成率重置)
+
+ {(stats.iteration_jumps ?? []).map((j, i) => (
+
+ {j.project}
+ 迭代 {j.from_iteration}→{j.to_iteration}
+
+ {(j.rate_before * 100).toFixed(0)}%→{(j.rate_after * 100).toFixed(0)}%
+ {j.rate_after < j.rate_before - 0.3 && " ⚠️"}
+
+
+ ))}
+
+ )}
+
+ {/* 停滞模块 */}
+ {(stats.stalled_modules ?? []).length > 0 && (
+
+
+ 停滞模块(in_progress 但无可推进任务)
+
+
+ {(stats.stalled_modules ?? []).map((m, i) => (
+ - {m}
+ ))}
+
+
+ )}
+
+ {/* Git 执行质量 */}
+
+
+
Git 执行质量
+
+
+ {ingestSummary && (
+
{ingestSummary}
+ )}
+ {commitHealth && commitHealth.length > 0 ? (
+
+
+
+
+ | 项目 |
+ commits |
+ 规范化 |
+ 返工率 |
+ TOP scope |
+
+
+
+ {commitHealth.map((h) => {
+ return (
+
+ |
+ {h.project_name}
+ |
+ {h.total_commits} |
+ = 0.8 ? "text-green-600" : h.conventional_rate >= 0.5 ? "text-amber-500" : "text-red-500"}`}>
+ {Math.round(h.conventional_rate * 100)}%
+ |
+ 0.2 ? "text-red-500" : h.rework_rate > 0.1 ? "text-amber-500" : "text-gray-500"}`}>
+ {Math.round(h.rework_rate * 100)}%
+ |
+
+ {h.top_scopes.slice(0, 2).map((s) => `${s.scope}(${s.commit_count})`).join(" ")}
+ |
+
+ );
+ })}
+
+
+
+ ) : (
+
+ 暂无数据——点"全量导入"从各项目 git 历史初始化
+
+ )}
+
+
+ {/* 梦核分析 */}
+
+
梦核分析
+
+ 生成包含以上数据的提示词,交给 Claude Opus 做固化/遗忘判断。
+
+ {!muhePrompt ? (
+
+ ) : (
+
+ )}
+
+ >
+ )}
+
+ );
+}
diff --git a/src/components/insight/GlobalGovernancePanel.tsx b/src/components/insight/GlobalGovernancePanel.tsx
new file mode 100644
index 0000000..5d93a61
--- /dev/null
+++ b/src/components/insight/GlobalGovernancePanel.tsx
@@ -0,0 +1,151 @@
+// 全局治理:CONVENTIONS 规则来源 + 应用梦核建议(写入 master 并同步所有项目)
+// 2026-07-02 ui-restructure N3:从单项目 GovernancePanel 迁出——这两块操作影响所有项目,属全局层
+import { useState } from "react";
+import { useQuery } from "@tanstack/react-query";
+import {
+ getUserConventionsStatus,
+ saveUserConventions,
+ resetConventionsToBuiltin,
+ type UserConventionsStatus,
+} from "../../lib/commands";
+
+export function GlobalGovernancePanel() {
+ const { data: userConventions, refetch: refetchUserConventions } = useQuery({
+ queryKey: ["user-conventions-status"],
+ queryFn: () => getUserConventionsStatus(),
+ });
+
+ const [conventionsInput, setConventionsInput] = useState("");
+ const [editorOpen, setEditorOpen] = useState(false);
+ const [saving, setSaving] = useState(false);
+ const [saveResult, setSaveResult] = useState(null);
+ const [saveError, setSaveError] = useState(null);
+
+ const handleSave = async () => {
+ if (!conventionsInput.trim()) return;
+ setSaving(true);
+ setSaveError(null);
+ setSaveResult(null);
+ try {
+ const res = await saveUserConventions(conventionsInput);
+ setSaveResult(`已应用 v${res.version},同步了 ${res.synced_count} 个项目`);
+ setConventionsInput("");
+ setEditorOpen(false);
+ refetchUserConventions();
+ } catch (e) {
+ setSaveError(String(e));
+ } finally {
+ setSaving(false);
+ }
+ };
+
+ const handleReset = async () => {
+ setSaving(true);
+ setSaveError(null);
+ setSaveResult(null);
+ try {
+ const res = await resetConventionsToBuiltin();
+ setSaveResult(`已重置为内置基线 v${res.version},同步了 ${res.synced_count} 个项目`);
+ refetchUserConventions();
+ } catch (e) {
+ setSaveError(String(e));
+ } finally {
+ setSaving(false);
+ }
+ };
+
+ return (
+
+
+
全局治理
+
CONVENTIONS 规则库——此处的变更同步到所有已接入项目
+
+
+ {/* 规则来源 */}
+ {userConventions && (
+
+ 规则来源
+ {userConventions.source === "user" ? (
+
+ 用户自定义 v{userConventions.version}
+ {userConventions.outdated_from_builtin && (
+ ⚠️ 低于内置
+ )}
+
+ ) : (
+
+ 内置基线 v{userConventions.builtin_version}
+
+ )}
+
+ )}
+
+ {/* 应用梦核建议 */}
+
+
+
应用梦核建议
+
+
+ {editorOpen && (
+
+
+ 将梦核 Opus 输出的新 CONVENTIONS 内容粘贴到下方,确认后将写入并同步所有项目。
+
+
+ )}
+ {!editorOpen && userConventions?.source === "user" && (
+
+
当前使用用户自定义规则
+
+
+ )}
+ {saveResult && (
+
✓ {saveResult}
+ )}
+
+
+
+ 项目级操作(同步规则到某项目、接入包、蓝图提示词)在各项目蓝图 → ⚙️ 治理面板中进行。
+
+
+ );
+}
diff --git a/src/components/insight/InsightPage.tsx b/src/components/insight/InsightPage.tsx
new file mode 100644
index 0000000..340a8c6
--- /dev/null
+++ b/src/components/insight/InsightPage.tsx
@@ -0,0 +1,52 @@
+// 洞察页:飞轮(跨项目数据反哺)/ 健康中心 / 全局治理
+// ui-restructure N3:跨项目内容的统一去处——单项目内容留在项目蓝图弹窗
+import { useState } from "react";
+import { FlywheelPanel } from "./FlywheelPanel";
+import { GlobalGovernancePanel } from "./GlobalGovernancePanel";
+import { HealthPage } from "../health/HealthPage";
+
+type InsightTab = "flywheel" | "health" | "governance";
+
+const TABS: { id: InsightTab; label: string }[] = [
+ { id: "flywheel", label: "♻️ 飞轮" },
+ { id: "health", label: "健康中心" },
+ { id: "governance", label: "全局治理" },
+];
+
+export function InsightPage() {
+ const [tab, setTab] = useState("flywheel");
+
+ return (
+
+
+ {TABS.map(({ id, label }) => (
+
+ ))}
+
+
+
+ {tab === "flywheel" && (
+
+
+
+ )}
+ {tab === "health" &&
}
+ {tab === "governance" && (
+
+
+
+ )}
+
+
+ );
+}
diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx
index 98e5164..210ad8e 100644
--- a/src/components/layout/Sidebar.tsx
+++ b/src/components/layout/Sidebar.tsx
@@ -5,7 +5,6 @@ import {
type Space,
} from "../../lib/commands";
import { useUIStore } from "../../store/ui";
-import { RepoRegistryModal } from "../dashboard/RepoRegistryModal";
export type Page = "overview" | "dashboard" | "resources" | "insight" | "tools" | "settings";
@@ -27,7 +26,6 @@ export function Sidebar({ page, onPageChange }: Props) {
const [spaceOpen, setSpaceOpen] = useState(true);
const [showForm, setShowForm] = useState(undefined);
const [deleteTarget, setDeleteTarget] = useState(null);
- const [showRepoRegistry, setShowRepoRegistry] = useState(false);
const qc = useQueryClient();
const showToast = useUIStore((s) => s.showToast);
@@ -154,19 +152,6 @@ export function Sidebar({ page, onPageChange }: Props) {
)}
- {/* 仓库管理 */}
-
-
-
-
{/* 新建/编辑模态框 */}
@@ -182,11 +167,6 @@ export function Sidebar({ page, onPageChange }: Props) {
/>
)}
- {/* 仓库管理弹窗 */}
- {showRepoRegistry && (
-
setShowRepoRegistry(false)} />
- )}
-
{/* 删除确认 */}
{deleteTarget && (
diff --git a/src/components/manage/ManagePage.tsx b/src/components/manage/ManagePage.tsx
index 3d9b144..7cb17d2 100644
--- a/src/components/manage/ManagePage.tsx
+++ b/src/components/manage/ManagePage.tsx
@@ -13,18 +13,19 @@ import { Dialog } from "../ui/Dialog";
import { TemplateEditor } from "./TemplateEditor";
import { NewProjectModal } from "./NewProjectModal";
import { TagsManager } from "./TagsManager";
+import { RegistryTab } from "../dashboard/RepoRegistryModal";
export function ManagePage() {
- const [tab, setTab] = useState<"projects" | "groups" | "templates" | "tags">("projects");
+ const [tab, setTab] = useState<"projects" | "groups" | "templates" | "tags" | "repos">("projects");
const { data: spaces = [] } = useQuery({ queryKey: ["spaces"], queryFn: getSpaces });
const currentSpaceId = spaces.find((s) => s.is_current)?.id;
- const TAB_LABELS = { projects: "项目", groups: "产品组", templates: "项目模板", tags: "标签" };
+ const TAB_LABELS = { projects: "项目", groups: "产品组", templates: "项目模板", tags: "标签", repos: "仓库" };
return (
- {(["projects", "groups", "templates", "tags"] as const).map((t) => (
+ {(["projects", "groups", "templates", "tags", "repos"] as const).map((t) => (
diff --git a/src/components/overview/OverviewPage.tsx b/src/components/overview/OverviewPage.tsx
index b684412..7907fdf 100644
--- a/src/components/overview/OverviewPage.tsx
+++ b/src/components/overview/OverviewPage.tsx
@@ -148,8 +148,8 @@ export function OverviewPage() {
-
仪表盘
-
{buffs.length} 个项目已施加蓝图 Buff
+
总览
+
{buffs.length} 个项目已施加蓝图 Buff · 跨项目行动项一览
{/* 跨项目进行中任务 */}
diff --git a/src/components/settings/SettingsPage.tsx b/src/components/settings/SettingsPage.tsx
index 2900754..a90f51d 100644
--- a/src/components/settings/SettingsPage.tsx
+++ b/src/components/settings/SettingsPage.tsx
@@ -12,6 +12,7 @@ import {
// GitHub OAuth 配置已移至侧边栏「应用设置」
import { useUIStore } from "../../store/ui";
import { Dialog } from "../ui/Dialog";
+import { GiteaInstancesSection } from "../dashboard/GiteaPanel";
// 编辑器条目数据结构(存为 JSON 保存到 settings 表)
interface EditorEntry {
@@ -549,6 +550,7 @@ export function SettingsPage() {
)}
+
);