制定统一 UI P0-P3 实施计划

This commit is contained in:
张家训
2026-08-01 12:42:14 +08:00
parent f9574143b8
commit d0c5f73e53
4 changed files with 1300 additions and 0 deletions
@@ -0,0 +1,259 @@
# WorkUtils Unified UI P1 API Document Export Implementation Plan
> **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:** Recompose the API document export plugin to match the approved segmented-mode form, honest progress/action row, and resizable execution log while preserving requests, proxy behavior, history, and base-class configuration.
**Architecture:** Keep `ApiDocExportRequest`, `RunApiDocExportUseCase`, `AsyncOperationController`, and `ExportHistoryPane` unchanged. Extract presentation-only controls from the 900-line `ApiDocWindow`: `ApiDocModeSelector` renders modes, `ApiDocExportForm` owns mode-specific inputs, and `ResizableLogPane` owns log layout; `ApiDocWindow` remains the composition and event-wiring boundary.
**Tech Stack:** Java 17, JavaFX 17.0.15, AtlantaFX 2.0.1, P0 `DesignTokens`/`IconFactory`, JUnit 5, Maven Wrapper.
## Global Constraints
- Preserve tabs exactly: “导出配置 / 导出历史 / 基类配置”.
- Preserve all three request modes and the explicit local-proxy checkbox; do not change request defaults or generated output.
- Use one segmented mode row instead of three oversized cards.
- Running state shows determinate progress when available, “导出中…”, and “取消” only when the operation has real cancellation support.
- Navigation and history viewing remain usable during export; only request-mutating inputs are disabled.
- The log pane is collapsible and vertically resizable, defaults to 10–14 visible lines, and uses the P0 mono token.
- Do not expose credentials, tokens, or credential-bearing URLs in logs.
- Follow TDD and preserve `LoadingButtonBinder` as the only loading-button renderer.
---
## File Structure
### Create
- `src/main/java/com/example/workutils/biz/apidoc/presentation/ApiDocModeSelector.java`
- `src/main/java/com/example/workutils/biz/apidoc/presentation/ApiDocExportForm.java`
- `src/main/java/com/example/workutils/system/ui/ResizableLogPane.java`
- `src/test/java/com/example/workutils/biz/apidoc/presentation/ApiDocModeSelectorTest.java`
- `src/test/java/com/example/workutils/biz/apidoc/presentation/ApiDocExportFormTest.java`
- `src/test/java/com/example/workutils/system/ui/ResizableLogPaneTest.java`
### Modify
- `src/main/java/com/example/workutils/biz/apidoc/ApiDocWindow.java`
- `src/main/resources/css/light-theme.css`
- `src/test/java/com/example/workutils/biz/apidoc/ApiDocWindowArchitectureTest.java`
- `src/test/java/com/example/workutils/biz/apidoc/ApiDocHistoryMappingTest.java`
## Task 1: Extract the segmented mode selector
**Files:**
- Create: `src/main/java/com/example/workutils/biz/apidoc/presentation/ApiDocModeSelector.java`
- Create: `src/test/java/com/example/workutils/biz/apidoc/presentation/ApiDocModeSelectorTest.java`
- Modify: `src/main/resources/css/light-theme.css`
**Interfaces:**
- Produces: `selectedModeProperty(): ObjectProperty<ApiDocExportRequest.Mode>`.
- Produces: `setMode(Mode)`, `getMode()`, and exactly three accessible toggle segments.
- [ ] **Step 1: Write the failing selector test**
```java
@Test
void exposesThreeModesAndUpdatesOneSelectedProperty() {
ApiDocModeSelector selector = fx(ApiDocModeSelector::new);
assertEquals(List.of(Mode.PATH, Mode.FQN, Mode.BATCH), selector.modes());
fx(() -> selector.setMode(Mode.FQN));
assertEquals(Mode.FQN, fx(selector::getMode));
assertEquals(1, fx(selector::selectedCount));
}
```
- [ ] **Step 2: Run and verify failure**
Run: `.\mvnw.cmd -q -Dtest=ApiDocModeSelectorTest test`
Expected: FAIL because the component is missing.
- [ ] **Step 3: Implement the selector**
Use a `ToggleGroup` and three `ToggleButton`s. Each segment uses P0 `AppIcon` values, visible title/helper labels, CSS class `segmented-mode`, and an accessible label containing both title and helper text. Prevent clearing the final selection.
- [ ] **Step 4: Run test and commit**
Run: `.\mvnw.cmd -q -Dtest=ApiDocModeSelectorTest test`
Expected: PASS.
```powershell
git add src/main/java/com/example/workutils/biz/apidoc/presentation/ApiDocModeSelector.java src/test/java/com/example/workutils/biz/apidoc/presentation/ApiDocModeSelectorTest.java src/main/resources/css/light-theme.css
git commit -m "重构 API 导出模式选择器"
```
## Task 2: Extract mode-specific form state without changing requests
**Files:**
- Create: `src/main/java/com/example/workutils/biz/apidoc/presentation/ApiDocExportForm.java`
- Create: `src/test/java/com/example/workutils/biz/apidoc/presentation/ApiDocExportFormTest.java`
- Modify: `src/main/java/com/example/workutils/biz/apidoc/ApiDocWindow.java`
**Interfaces:**
- Consumes: `ApiDocModeSelector`, existing project/module data, and proxy default.
- Produces: `buildRequest(): ApiDocExportRequest`, `requestControls(): List<Control>`, `setRequestControlsDisabled(boolean)`.
- Produces testable setters `setPathInput(String)`, `setFqnInput(String)`, `setBatchProjectPaths(List<String>)`, and `setDebugProxyEnabled(boolean)`; production event handlers use the same properties.
- [ ] **Step 1: Write failing request-equivalence tests**
```java
private static ApiDocExportForm fixtureForm(ApiDocExportRequest.Mode mode) {
ApiDocModeSelector selector = new ApiDocModeSelector();
selector.setMode(mode);
ApiDocExportForm form = new ApiDocExportForm(selector, List.of("D:/demo"), false);
form.setPathInput("D:/demo/OrderFacade.java");
form.setFqnInput("com.example.OrderFacade");
form.setBatchProjectPaths(List.of("D:/demo"));
form.setDebugProxyEnabled(true);
return form;
}
@ParameterizedTest
@EnumSource(ApiDocExportRequest.Mode.class)
void buildsTheSameRequestFieldsForEveryMode(ApiDocExportRequest.Mode mode) {
ApiDocExportForm form = fx(() -> fixtureForm(mode));
ApiDocExportRequest request = fx(form::buildRequest);
assertEquals(mode, request.mode());
switch (mode) {
case PATH -> assertEquals("D:/demo/OrderFacade.java", request.input());
case FQN -> assertEquals("com.example.OrderFacade", request.input());
case BATCH -> assertEquals(List.of("D:/demo"), request.projectPaths());
}
assertTrue(request.debugProxyEnabled());
}
```
- [ ] **Step 2: Run and verify failure**
Run: `.\mvnw.cmd -q -Dtest=ApiDocExportFormTest test`
Expected: FAIL because `ApiDocExportForm` is missing.
- [ ] **Step 3: Move presentation and request extraction**
Move the current custom path, FQN, batch project/module inputs, and validation messages into the form. Preserve the existing `ApiDocExportRequest` constructor/factory calls verbatim. Render label above input and helper below input; keep proxy as an explicit checkbox with helper text.
- [ ] **Step 4: Delegate `ApiDocWindow.buildRequest()` to the form**
`ApiDocWindow` may catch/display `IOException`, but it must no longer inspect individual mode controls. Bind selector mode to the form’s visible managed section.
- [ ] **Step 5: Run request/use-case tests**
Run: `.\mvnw.cmd -q -Dtest=ApiDocExportFormTest,ApiDocExportRequestTest,RunApiDocExportUseCaseTest,ApiDocGeneratorProxyTest test`
Expected: PASS with unchanged proxy and generation semantics.
- [ ] **Step 6: Commit**
```powershell
git add src/main/java/com/example/workutils/biz/apidoc/presentation/ApiDocExportForm.java src/main/java/com/example/workutils/biz/apidoc/ApiDocWindow.java src/test/java/com/example/workutils/biz/apidoc/presentation/ApiDocExportFormTest.java
git commit -m "拆分 API 导出表单状态"
```
## Task 3: Add the approved resizable log pane
**Files:**
- Create: `src/main/java/com/example/workutils/system/ui/ResizableLogPane.java`
- Create: `src/test/java/com/example/workutils/system/ui/ResizableLogPaneTest.java`
- Modify: `src/main/resources/css/light-theme.css`
- Modify: `src/main/java/com/example/workutils/biz/apidoc/ApiDocWindow.java`
**Interfaces:**
- Produces: `append(String)`, `clear()`, `expandedProperty()`, `setExpanded(boolean)`, `lineCountProperty()`, and package-visible `isBodyManaged()` for component verification.
- Exposes a read-only `TextArea`; default preferred height must fit 12 mono lines.
- [ ] **Step 1: Write failing component tests**
```java
@Test
void countsLinesAndCollapseRemovesBodyFromLayout() {
ResizableLogPane pane = fx(() -> new ResizableLogPane("执行日志", 12));
fx(() -> pane.append("one\ntwo"));
assertEquals(2, fx(() -> pane.lineCountProperty().get()));
fx(() -> pane.setExpanded(false));
assertFalse(fx(pane::isBodyManaged));
}
```
- [ ] **Step 2: Run and verify failure**
Run: `.\mvnw.cmd -q -Dtest=ResizableLogPaneTest test`
Expected: FAIL because the component is missing.
- [ ] **Step 3: Implement and integrate**
Use a header, read-only text area, footer line count, collapse control, and resize grip/split divider that never overlays log text. Replace direct `logArea.appendText` calls with `logPane.append`; keep redaction at the existing message-producing boundary.
- [ ] **Step 4: Run tests and commit**
Run: `.\mvnw.cmd -q -Dtest=ResizableLogPaneTest,ApiDocWindowArchitectureTest test`
Expected: PASS.
```powershell
git add src/main/java/com/example/workutils/system/ui/ResizableLogPane.java src/test/java/com/example/workutils/system/ui/ResizableLogPaneTest.java src/main/java/com/example/workutils/biz/apidoc/ApiDocWindow.java src/main/resources/css/light-theme.css
git commit -m "增加可调整执行日志面板"
```
## Task 4: Recompose the API page and synchronize operation state
**Files:**
- Modify: `src/main/java/com/example/workutils/biz/apidoc/ApiDocWindow.java`
- Modify: `src/test/java/com/example/workutils/biz/apidoc/ApiDocWindowArchitectureTest.java`
- Modify: `src/test/java/com/example/workutils/biz/apidoc/ApiDocHistoryMappingTest.java`
**Interfaces:**
- Consumes: `ApiDocModeSelector`, `ApiDocExportForm`, `OperationStatusPane`, `ResizableLogPane`.
- Preserves: `getContentPane()`, `toHistoryItem(HistoryRow)`, all three tabs, and history persistence.
- [ ] **Step 1: Strengthen the failing architecture test**
```java
assertTrue(source.contains("new ApiDocModeSelector()"));
assertTrue(source.contains("new ApiDocExportForm("));
assertTrue(source.contains("new ResizableLogPane(\"执行日志\", 12)"));
assertFalse(source.contains("buildModeCard("));
assertFalse(source.contains("setGraphic(btnBox)"));
```
- [ ] **Step 2: Run and verify failure**
Run: `.\mvnw.cmd -q -Dtest=ApiDocWindowArchitectureTest test`
Expected: FAIL on the old card/action-cell implementation.
- [ ] **Step 3: Build the final layout**
Compose tabs, selector, form, a progress/action row, and log pane in that order. Use `ExportHistoryPane` for history and keep base-class configuration content unchanged. Remove the duplicated history action cell code only after mapping tests prove shared history equivalence.
- [ ] **Step 4: Bind disablement narrowly**
Bind `form.setRequestControlsDisabled(snapshot.isBusy())`; leave tabs, navigation, and history controls enabled. Bind the export button through `LoadingButtonBinder` and show cancel only if `RunApiDocExportUseCase` cancellation reaches its operation context; otherwise omit cancel rather than simulate it.
- [ ] **Step 5: Run all API tests**
Run: `.\mvnw.cmd -q -Dtest=ApiDocModeSelectorTest,ApiDocExportFormTest,ApiDocWindowArchitectureTest,ApiDocHistoryMappingTest,ApiDocExportRequestTest,RunApiDocExportUseCaseTest,ApiDocGeneratorProxyTest test`
Expected: PASS.
- [ ] **Step 6: Perform read-only visual comparison**
Capture “导出配置” idle and synthetic/controlled running state at the same viewport as the approved reference. Compare reference and implementation side by side for typography, segmented selector, input alignment, progress/action row, and log dimensions. Do not perform a real export or start the local proxy during visual verification.
- [ ] **Step 7: Run P1 completion gate and commit**
Run: `.\mvnw.cmd -q test`
Run: `.\mvnw.cmd -q -DskipTests compile`
Run: `git diff --check`
Expected: all exit 0.
```powershell
git add src/main/java/com/example/workutils/biz/apidoc src/main/java/com/example/workutils/system/ui/ResizableLogPane.java src/main/resources/css/light-theme.css src/test/java/com/example/workutils/biz/apidoc src/test/java/com/example/workutils/system/ui/ResizableLogPaneTest.java
git commit -m "按统一规范重排 API 文档导出"
```