260 lines
12 KiB
Markdown
260 lines
12 KiB
Markdown
# 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 文档导出"
|
||
```
|