Files
workutils/docs/superpowers/plans/2026-07-30-plugin-ui-refactor-phase-1.md
2026-07-30 22:06:04 +08:00

9.4 KiB

Plugin UI Refactor Phase 1 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: Introduce one testable asynchronous operation model and migrate Scanner and MyBatis SQL Export to consistent loading, cancellation, progress, error recovery, and history UI behavior.

Architecture: Add JavaFX-neutral operation state in system/ui/async, with a JavaFX adapter that binds buttons and status controls. Plugin windows submit immutable requests to operation controllers; services publish structured progress. Existing business services remain intact in this phase.

Tech Stack: Java 17, JavaFX 17, Spring Boot 2.7, JUnit 5, Maven.

Global Constraints

  • Preserve all existing command-line plugin behavior.
  • Do not add raw new Thread calls.
  • Do not put Git, SQL, HTTP, process, or file traversal logic in Window classes.
  • Use enum state instead of boolean combinations.
  • Give unrelated user actions independent operation state.
  • Keep production files below 350 lines where practical; do not increase existing Window line counts.
  • Do not modify the user's existing checkout; work only in the isolated worktree.

Task 1: Operation State Machine

Files:

  • Create: src/main/java/com/example/workutils/system/ui/async/OperationState.java
  • Create: src/main/java/com/example/workutils/system/ui/async/OperationSnapshot.java
  • Create: src/main/java/com/example/workutils/system/ui/async/OperationStateMachine.java
  • Test: src/test/java/com/example/workutils/system/ui/async/OperationStateMachineTest.java

Interfaces:

  • Produces: OperationState, immutable OperationSnapshot, and OperationStateMachine.

  • OperationStateMachine.start(String), progress(String,double), requestCancel(), succeed(String), fail(String), and reset().

  • Invalid transitions throw IllegalStateException.

  • Step 1: Write failing state-transition tests

@Test
void runningOperationCanProgressAndSucceed() {
    OperationStateMachine machine = new OperationStateMachine();
    machine.start("扫描中");
    machine.progress("项目 1/3", 1.0 / 3.0);
    machine.succeed("扫描完成");

    assertEquals(OperationState.SUCCEEDED, machine.snapshot().state());
    assertEquals("扫描完成", machine.snapshot().message());
}

@Test
void idleOperationCannotBeCancelled() {
    OperationStateMachine machine = new OperationStateMachine();
    assertThrows(IllegalStateException.class, machine::requestCancel);
}
  • Step 2: Run tests and verify RED

Run: .\mvnw.cmd -q -Dtest=OperationStateMachineTest test

Expected: compilation failure because the production types do not exist.

  • Step 3: Implement the minimal state machine

Implement explicit legal transitions:

IDLE -> RUNNING
RUNNING -> CANCELLING | SUCCEEDED | FAILED
CANCELLING -> SUCCEEDED | FAILED
SUCCEEDED | FAILED -> IDLE
  • Step 4: Run tests and verify GREEN

Run: .\mvnw.cmd -q -Dtest=OperationStateMachineTest test

Expected: all tests pass.

Task 2: Asynchronous Operation Controller

Files:

  • Create: src/main/java/com/example/workutils/system/ui/async/OperationTask.java
  • Create: src/main/java/com/example/workutils/system/ui/async/OperationContext.java
  • Create: src/main/java/com/example/workutils/system/ui/async/AsyncOperationController.java
  • Create: src/main/java/com/example/workutils/system/support/AppExecutors.java
  • Test: src/test/java/com/example/workutils/system/ui/async/AsyncOperationControllerTest.java

Interfaces:

  • Consumes: OperationStateMachine.

  • Produces: one controller with start(OperationTask<T>, Consumer<T>), cancel(), snapshotProperty(), and runningProperty().

  • OperationContext supplies report(message, progress) and isCancellationRequested().

  • Step 1: Write failing success, failure, and cancellation tests

Use a single-thread test executor and CountDownLatch. Assert observable controller state, not mock calls.

  • Step 2: Run tests and verify RED

Run: .\mvnw.cmd -q -Dtest=AsyncOperationControllerTest test

Expected: compilation failure because the controller does not exist.

  • Step 3: Implement the minimal controller

Use the injected ExecutorService; marshal observable property changes through an injected Consumer<Runnable> so tests can use Runnable::run and production can use Platform::runLater.

  • Step 4: Run tests and verify GREEN

Run: .\mvnw.cmd -q -Dtest=AsyncOperationControllerTest test

Expected: success, failure, and cancellation tests pass.

Task 3: Loading and Status Components

Files:

  • Create: src/main/java/com/example/workutils/system/ui/async/LoadingButtonBinder.java
  • Create: src/main/java/com/example/workutils/system/ui/async/OperationStatusPane.java
  • Modify: src/main/resources/css/light-theme.css
  • Test: src/test/java/com/example/workutils/system/ui/async/LoadingButtonTextTest.java

Interfaces:

  • Consumes: AsyncOperationController.

  • Produces: consistent primary-button loading text, conditional cancel button, progress indicator, status message, and error/success style classes.

  • Step 1: Write a failing pure text-mapping test

Test LoadingButtonBinder.textFor(snapshot, idleText) with literal expectations:

assertEquals("扫描中…", textFor(running("扫描中"), "开始扫描"));
assertEquals("正在停止…", textFor(cancelling(), "开始扫描"));
assertEquals("开始扫描", textFor(idle(), "开始扫描"));
  • Step 2: Run tests and verify RED

Run: .\mvnw.cmd -q -Dtest=LoadingButtonTextTest test

  • Step 3: Implement mapper, binder, and status pane

Use a real JavaFX ProgressIndicator only in the binder. Keep text selection pure and independently testable.

  • Step 4: Run tests and compile

Run: .\mvnw.cmd -q -Dtest=LoadingButtonTextTest test

Run: .\mvnw.cmd -q -DskipTests compile

Task 4: Scanner Migration

Files:

  • Create: src/main/java/com/example/workutils/biz/scanner/application/ScanRequest.java
  • Create: src/main/java/com/example/workutils/biz/scanner/application/ScanProgress.java
  • Create: src/main/java/com/example/workutils/biz/scanner/application/RunScanUseCase.java
  • Modify: src/main/java/com/example/workutils/biz/scanner/ScannerWindow.java
  • Test: src/test/java/com/example/workutils/biz/scanner/application/ScanRequestTest.java

Interfaces:

  • Consumes: ScannerService, AsyncOperationController.

  • Produces: immutable validated ScanRequest and structured ScanProgress.

  • Step 1: Write failing ScanRequest validation tests

Test blank keyword rejection, invalid regular expression rejection, and trimmed keyword preservation.

  • Step 2: Run tests and verify RED

Run: .\mvnw.cmd -q -Dtest=ScanRequestTest test

  • Step 3: Implement request and use case

The use case adapts existing ScannerService callbacks/results without changing scan behavior.

  • Step 4: Migrate ScannerWindow

Replace raw thread creation and manual scanBtn.setDisable with AsyncOperationController. Add OperationStatusPane; display the cancel button only while running. Keep history behavior unchanged.

  • Step 5: Run focused and full tests

Run: .\mvnw.cmd -q -Dtest=ScanRequestTest test

Run: .\mvnw.cmd -q test

Task 5: MyBatis SQL Export Migration

Files:

  • Create: src/main/java/com/example/workutils/biz/mybatisexport/application/SqlExportRequest.java
  • Create: src/main/java/com/example/workutils/biz/mybatisexport/application/SqlExportProgress.java
  • Create: src/main/java/com/example/workutils/biz/mybatisexport/application/RunSqlExportUseCase.java
  • Modify: src/main/java/com/example/workutils/biz/mybatisexport/MyBatisExportWindow.java
  • Test: src/test/java/com/example/workutils/biz/mybatisexport/application/SqlExportRequestTest.java

Interfaces:

  • Consumes: MyBatisExportService, AsyncOperationController.

  • Produces: immutable validated request and structured progress.

  • Step 1: Write failing filter validation tests

Test that ALL requires no value, TABLE requires a nonblank table, and STATEMENT requires a nonblank statement id.

  • Step 2: Run tests and verify RED

Run: .\mvnw.cmd -q -Dtest=SqlExportRequestTest test

  • Step 3: Implement request and use case

Adapt current export service without changing CSV format or history persistence.

  • Step 4: Migrate MyBatisExportWindow

Replace raw thread and manual button toggles with the shared controller. Show progress, current project, and conditional cancel action.

  • Step 5: Run focused and full tests

Run: .\mvnw.cmd -q -Dtest=SqlExportRequestTest test

Run: .\mvnw.cmd -q test

Task 6: Phase Verification

Files:

  • Modify: docs/audit/plugin-ui-code-audit.md only if implementation differs from the approved design.

Interfaces:

  • Consumes: all previous tasks.

  • Produces: a compiling, tested first phase ready for UI inspection.

  • Step 1: Run all tests

Run: .\mvnw.cmd -q test

  • Step 2: Compile production code

Run: .\mvnw.cmd -q -DskipTests compile

  • Step 3: Inspect changes

Run: git diff --check

Run: git status --short

  • Step 4: Launch and visually verify

Verify Scanner and MyBatis SQL Export in IDLE, RUNNING, CANCELLING, FAILED, and SUCCEEDED states without triggering destructive operations.