From c21a3c43a1f05b71c5d291e9aa0734fe1457a0d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=AE=B6=E8=AE=AD?= Date: Wed, 5 Aug 2026 11:16:08 +0800 Subject: [PATCH] docs: plan AI test plugin UI optimization --- ...26-08-05-ai-test-plugin-ui-optimization.md | 237 ++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 docs/superpowers/plans/2026-08-05-ai-test-plugin-ui-optimization.md diff --git a/docs/superpowers/plans/2026-08-05-ai-test-plugin-ui-optimization.md b/docs/superpowers/plans/2026-08-05-ai-test-plugin-ui-optimization.md new file mode 100644 index 0000000..35207fd --- /dev/null +++ b/docs/superpowers/plans/2026-08-05-ai-test-plugin-ui-optimization.md @@ -0,0 +1,237 @@ +# AI 单测插件完整 UI 优化实施计划 + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 将 AI 单测插件改为四个主 Tab 的低密度工作台,并提供目录选择、准确的模块反馈和只在主动任务期间出现的全局进度。 + +**Architecture:** `AiTestPane` 保留工作流编排,但把可独立验证的行为下沉到 `DirectoryPathSelector`、`TaskProgressState` 和现有 `StartModuleSelector`。项目加载继续使用 `LatestRequestGate`,Git 分析继续传递显式 `baseline` 与 `targetRef`。 + +**Tech Stack:** Java 17、JavaFX 17、Spring Boot 2.7、Maven、JUnit 5 + +## Global Constraints + +- 主页面固定为 `项目与增量`、`工具链`、`质量规范`、`结果与合并` 四个不可关闭 Tab,不再使用 Accordion。 +- JDK 8 Home 与 Maven Home 必须通过目录选择器选择,路径框只读。 +- 基线默认 `master`,目标默认 `HEAD`;不得破坏任意 `baseline...targetRef` 分析能力。 +- 项目加载不得进入全局 busy,较早异步结果不得覆盖最新选择。 +- 空闲时全局进度条隐藏并重置;仅用户主动的分析、生成、验证、合并和工具链检查显示。 +- 真实 `ft-loancapital-core` 必须继续检出 `ft-loancapital-core-start`。 +- 不触碰现有未跟踪的审计文档、截图目录和 SQL 文件。 +- 全程单线程实施,不使用子代理。 + +--- + +### Task 1: 可测试的目录选择组件 + +**Files:** +- Create: `src/main/java/com/example/workutils/biz/aitest/ui/DirectoryPathSelector.java` +- Test: `src/test/java/com/example/workutils/biz/aitest/ui/DirectoryPathSelectorTest.java` + +**Interfaces:** +- Produces: `DirectoryPathSelector(String label, Supplier ownerSupplier)` +- Produces: `Path getPath()`、`void setPath(Path)`、`TextField pathField()`、`Button chooseButton()` +- Internal test seam: package-private `void applySelection(File selected)`,`null` 表示取消。 + +- [ ] **Step 1: 写失败测试** + +```java +@Test +void pathIsReadOnlyAndCancelKeepsExistingValue() throws Exception { + DirectoryPathSelector selector = FxTestSupport.fx( + () -> new DirectoryPathSelector("JDK 8 Home", () -> null)); + FxTestSupport.fx(() -> selector.setPath(Path.of("C:/jdk8"))); + FxTestSupport.fx(() -> selector.applySelection(null)); + assertFalse(FxTestSupport.fx(() -> selector.pathField().isEditable())); + assertEquals(Path.of("C:/jdk8"), FxTestSupport.fx(selector::getPath)); +} + +@Test +void selectedDirectoryUpdatesValue() throws Exception { + DirectoryPathSelector selector = FxTestSupport.fx( + () -> new DirectoryPathSelector("Maven Home", () -> null)); + FxTestSupport.fx(() -> selector.applySelection(new File("C:/maven"))); + assertEquals(Path.of("C:/maven"), FxTestSupport.fx(selector::getPath)); +} +``` + +- [ ] **Step 2: 运行 RED** + +Run: `mvn -Dtest=DirectoryPathSelectorTest test` + +Expected: FAIL,因为 `DirectoryPathSelector` 尚不存在。 + +- [ ] **Step 3: 实现组件** + +组件继承 `HBox`,内部包含只读 `TextField` 与文字按钮“选择目录”。按钮创建 `DirectoryChooser`;初始目录仅在当前路径存在且为目录时设置。`applySelection(null)` 不改变路径。 + +- [ ] **Step 4: 运行 GREEN** + +Run: `mvn -Dtest=DirectoryPathSelectorTest test` + +Expected: 2 tests,0 failures。 + +- [ ] **Step 5: 提交** + +```bash +git add src/main/java/com/example/workutils/biz/aitest/ui/DirectoryPathSelector.java src/test/java/com/example/workutils/biz/aitest/ui/DirectoryPathSelectorTest.java +git commit -m "feat: add directory selectors for AI test toolchain" +``` + +### Task 2: 进度状态与模块展示策略 + +**Files:** +- Create: `src/main/java/com/example/workutils/biz/aitest/ui/TaskProgressState.java` +- Create: `src/main/java/com/example/workutils/biz/aitest/project/ModuleLoadPresentation.java` +- Test: `src/test/java/com/example/workutils/biz/aitest/ui/TaskProgressStateTest.java` +- Test: `src/test/java/com/example/workutils/biz/aitest/project/ModuleLoadPresentationTest.java` + +**Interfaces:** +- Produces: `TaskProgressState.start()`、`update(double)`、`finish()`,以及 `visible()`、`progress()`。 +- Consumes: `StartModuleSelector.autoSelect(MavenProjectModel)`。 +- Produces: `ModuleLoadPresentation.from(MavenProjectModel, String preferredArtifactId)`,返回 `moduleCount()`、`selectedModule()` 与 `message()`。 + +- [ ] **Step 1: 写失败测试** + +```java +@Test +void taskProgressIsHiddenUntilStartedAndResetsWhenFinished() { + TaskProgressState state = new TaskProgressState(); + assertFalse(state.visible()); + state.start(); + assertTrue(state.visible()); + state.update(0.5); + assertEquals(0.5, state.progress()); + state.finish(); + assertFalse(state.visible()); + assertEquals(0.0, state.progress()); +} + +@Test +void uniqueStartModuleIsSelectedAndCountIsReported() { + ModuleLoadPresentation view = ModuleLoadPresentation.from(model("core", "app-start"), ""); + assertEquals(2, view.moduleCount()); + assertEquals("app-start", view.selectedModule().orElseThrow().artifactId()); + assertEquals("已检出 2 个 Maven 模块,已选择 app-start", view.message()); +} + +@Test +void multipleStartModulesRequireManualSelection() { + ModuleLoadPresentation view = ModuleLoadPresentation.from(model("a-start", "b-start"), ""); + assertTrue(view.selectedModule().isEmpty()); + assertEquals("已检出 2 个 Maven 模块,请选择生成模块", view.message()); +} +``` + +- [ ] **Step 2: 运行 RED** + +Run: `mvn -Dtest=TaskProgressStateTest,ModuleLoadPresentationTest test` + +Expected: FAIL,因为两个状态类型尚不存在。 + +- [ ] **Step 3: 实现最小状态类型** + +`TaskProgressState` 将进度约束在 `0..1`;`finish()` 固定恢复隐藏和 0。`ModuleLoadPresentation` 优先使用仍存在的用户偏好,否则调用 `StartModuleSelector`,并生成包含模块数量的中文状态。 + +- [ ] **Step 4: 运行 GREEN** + +Run: `mvn -Dtest=TaskProgressStateTest,ModuleLoadPresentationTest,StartModuleSelectorTest test` + +Expected: 全部通过。 + +- [ ] **Step 5: 提交** + +```bash +git add src/main/java/com/example/workutils/biz/aitest/ui/TaskProgressState.java src/main/java/com/example/workutils/biz/aitest/project/ModuleLoadPresentation.java src/test/java/com/example/workutils/biz/aitest/ui/TaskProgressStateTest.java src/test/java/com/example/workutils/biz/aitest/project/ModuleLoadPresentationTest.java +git commit -m "feat: model AI test progress and module loading states" +``` + +### Task 3: 四 Tab 页面与低密度表单 + +**Files:** +- Modify: `src/main/java/com/example/workutils/biz/aitest/AiTestPane.java` +- Replace: `src/test/java/com/example/workutils/biz/aitest/AiTestPaneArchitectureTest.java` + +**Interfaces:** +- Consumes: `DirectoryPathSelector`、`TaskProgressState`、`ModuleLoadPresentation`。 +- Preserves: `AiTestRequest(project, module, baseline, targetRef, includeWorkingTree, settings)`。 + +- [ ] **Step 1: 写失败的 JavaFX 结构测试** + +测试通过真实 `AiTestPane` 的节点树断言:中心是含四个指定 Tab 的 `TabPane`;不存在 `Accordion`;JDK/Maven 选择器路径框不可编辑;初始进度条不可见;基线和目标值分别为 `master` 与 `HEAD`。依赖使用 Mockito 返回空项目和空历史,测试只断言真实控件行为,不检查源码字符串。 + +- [ ] **Step 2: 运行 RED** + +Run: `mvn -Dtest=AiTestPaneArchitectureTest test` + +Expected: FAIL,因为中心仍是包含 Accordion 的 VBox,且进度条初始可见。 + +- [ ] **Step 3: 重构 `buildContent()`** + +创建四个不可关闭主 Tab:项目页放项目、模块、baseline、targetRef、工作区选项和模块状态;工具链页放两个目录组件、CodeFree、覆盖率、修复轮次和检查按钮;质量规范页放左右 SplitPane;结果页放任务按钮、结果二级 Tab 与审核合并卡片。 + +- [ ] **Step 4: 接入目录组件** + +删除 `jdk8Field`、`mavenField`,`settingsFromForm()` 改读 `jdkSelector.getPath()` 与 `mavenSelector.getPath()`;`applySettings()` 改调 `setPath()`。 + +- [ ] **Step 5: 接入模块展示** + +`applyProjectData()` 使用 `ModuleLoadPresentation.from(...)`,一次性更新模块、选中值和状态文案;继续在成功和失败回调前检查 `projectLoadGate.isLatest(projectRequest)`。 + +- [ ] **Step 6: 接入进度展示** + +初始配置加载、项目加载和模块偏好保存使用静默后台任务;`runAsync` 仅供用户主动任务。`setBusy(true)` 显示进度,`setBusy(false)` 隐藏并重置。项目页状态展示项目加载,不触发全局进度。 + +- [ ] **Step 7: 运行 GREEN** + +Run: `mvn -Dtest=AiTestPaneArchitectureTest,LatestRequestGateTest,DirectoryPathSelectorTest,TaskProgressStateTest,ModuleLoadPresentationTest test` + +Expected: 全部通过。 + +- [ ] **Step 8: 提交** + +```bash +git add src/main/java/com/example/workutils/biz/aitest/AiTestPane.java src/test/java/com/example/workutils/biz/aitest/AiTestPaneArchitectureTest.java +git commit -m "feat: reorganize AI test plugin into four tabs" +``` + +### Task 4: 回归保护与最终验证 + +**Files:** +- Modify only if a regression is exposed: files already listed in Tasks 1-3 +- Verify: `src/test/java/com/example/workutils/biz/aitest/project/FtLoanCapitalCoreRegressionTest.java` +- Verify: `src/test/java/com/example/workutils/biz/aitest/git/GitIncrementAnalyzerTest.java` + +**Interfaces:** +- Preserves: `master...DEV_1156339215001460262` real-repository regression. +- Preserves: latest project request wins. + +- [ ] **Step 1: 运行 AI 插件测试** + +Run: `mvn -Dtest='com.example.workutils.biz.aitest.**' test` + +Expected: 0 failures;真实仓库存在时 `FtLoanCapitalCoreRegressionTest` 不跳过。 + +- [ ] **Step 2: 运行全量测试** + +Run: `mvn test` + +Expected: 0 failures,0 errors;仅允许既有无显示环境 GUI 测试跳过。 + +- [ ] **Step 3: 打包** + +Run: `mvn -DskipTests package` + +Expected: `target/workutils-1.0.1-SNAPSHOT.jar` 生成成功。 + +- [ ] **Step 4: 差异和工作树检查** + +Run: `git diff --check` 与 `git status --short` + +Expected: 无空白错误;用户原有审计文档、截图目录和 SQL 仍保持未跟踪且未暂存。 + +- [ ] **Step 5: 提交验证修正(仅当 Step 1-4 暴露并修复问题时)** + +```bash +git add src/main/java/com/example/workutils/biz/aitest src/test/java/com/example/workutils/biz/aitest +git commit -m "fix: complete AI test plugin UI regression coverage" +```