# 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** ```java @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: ```text 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, Consumer)`, `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` 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: ```java 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.