12 KiB
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
LoadingButtonBinderas the only loading-button renderer.
File Structure
Create
src/main/java/com/example/workutils/biz/apidoc/presentation/ApiDocModeSelector.javasrc/main/java/com/example/workutils/biz/apidoc/presentation/ApiDocExportForm.javasrc/main/java/com/example/workutils/system/ui/ResizableLogPane.javasrc/test/java/com/example/workutils/biz/apidoc/presentation/ApiDocModeSelectorTest.javasrc/test/java/com/example/workutils/biz/apidoc/presentation/ApiDocExportFormTest.javasrc/test/java/com/example/workutils/system/ui/ResizableLogPaneTest.java
Modify
src/main/java/com/example/workutils/biz/apidoc/ApiDocWindow.javasrc/main/resources/css/light-theme.csssrc/test/java/com/example/workutils/biz/apidoc/ApiDocWindowArchitectureTest.javasrc/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
@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 ToggleButtons. 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.
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>), andsetDebugProxyEnabled(boolean); production event handlers use the same properties. -
Step 1: Write failing request-equivalence tests
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
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-visibleisBodyManaged()for component verification. -
Exposes a read-only
TextArea; default preferred height must fit 12 mono lines. -
Step 1: Write failing component tests
@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.
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
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.
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 文档导出"