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 Threadcalls. - 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, immutableOperationSnapshot, andOperationStateMachine. -
OperationStateMachine.start(String),progress(String,double),requestCancel(),succeed(String),fail(String), andreset(). -
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(), andrunningProperty(). -
OperationContextsuppliesreport(message, progress)andisCancellationRequested(). -
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
ScanRequestand structuredScanProgress. -
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.mdonly 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.