Files
workutils/docs/superpowers/plans/2026-08-01-workutils-unified-ui-p1-api-doc.md
T
2026-08-01 12:42:14 +08:00

12 KiB
Raw Blame History

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

@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>), and setDebugProxyEnabled(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-visible isBodyManaged() 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 文档导出"