重构插件异步状态与业务用例

This commit is contained in:
张家训
2026-07-30 22:06:04 +08:00
parent 6b855a42b8
commit 09fbd84837
72 changed files with 3723 additions and 904 deletions
+58
View File
@@ -0,0 +1,58 @@
# 插件 UI 与代码重构后续事项
更新时间:2026-07-30
## 已完成
- 建立统一异步状态、任务控制器、线程池、Loading 按钮绑定和状态面板。
- Scanner、MyBatis SQL 导出接入统一异步流程。
- 提取通用空状态、导出历史和历史文件操作组件。
- API 文档导出拆分请求、状态、用例和历史逻辑;本地调试代理改为显式选项且默认关闭。
- Git 同步完成 URL 脱敏、搜索过滤、请求与用例拆分。
- 文本对比提取 `DiffEngine`,耗时操作移出 UI 线程。
- MyBatis 逆向工程完成数据源脱敏、查询与生成独立状态、不可变请求及重置配置。
## 明日待办(按优先级)
### P0:生成输出目录与结果反馈
- 提取 `OutputLayoutPolicy`,由项目根目录统一推导 Model、XML Mapper 和 Mapper 接口目录。
- 生成前展示将创建、覆盖和跳过的文件摘要。
- 生成完成后展示文件清单,并提供打开目录、复制路径操作。
- 明确目录不可写、文件冲突和部分生成失败时的恢复策略。
- 为路径推导、覆盖策略和生成结果编写单元测试。
### P1:手机 UI 自动化拆分
- 将 `MobileUiAutomationWindow` 拆为运行用例、录制用例、环境与设备三个页面。
- 引入独立的 `RunState`、`RecordState` 和对应 ViewModel。
- 将截图轮询封装为可关闭的 `ScreenshotStream`,绑定到页面生命周期。
- 分离运行、停止、录制和生成操作,禁止依赖按钮文字判断状态。
- 优先补状态迁移和生命周期测试,再迁移 UI。
### P2:系统设置结构化
- 将设置按“常规、项目与数据源、插件、外观”分类。
- 用带校验的表单替代主要依赖外部编辑器修改配置文件。
- 敏感字段统一显示/隐藏,并确保列表和摘要只接触脱敏 DTO。
- 增加保存成功、校验失败和恢复默认的明确反馈。
### P3:公共基础能力收尾
- 评估并提取统一 `NotificationCenter`,避免各窗口自行弹窗。
- 为 Scanner、SQL 导出补充结构化进度 DTO;仅在底层支持真实中断时显示停止操作。
- 统一生成/导出插件的空状态、结果摘要、打开目录和复制路径交互。
- 补充键盘访问、焦点顺序、屏幕阅读器语义和颜色对比专项检查。
### 暂缓:Git 评审
- 按当前决定暂不重构 Git 评审。
- 后续如恢复,应单独立项,不与手机 UI 自动化或其他高风险窗口同时修改。
## 继续实施约束
- 每次只推进一个优先级阶段,先写请求、状态和用例测试,再迁移 UI。
- Window 不直接承载 Git、SQL、HTTP、进程命令或文件遍历。
- 每个操作使用独立状态,禁止用共享 `running` 锁住无关按钮。
- 跨线程数据使用不可变 DTO;敏感信息不得进入 UI 状态或日志。
- 每阶段完成后运行全量测试、编译、`git diff --check` 和只读 UI 验收。
@@ -0,0 +1,92 @@
# API Doc Refactor Phase 3 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:** Give API document export one immutable request, one async operation owner, an explicit debug-proxy option, and the shared history UI.
**Architecture:** Capture all JavaFX form values into `ApiDocExportRequest` on the UI thread, then execute from `AsyncOperationController`. Inject the debug endpoint into `ApiDocGenerator`; only the export use case may decide whether to start the local proxy. Adapt existing history rows to `HistoryItem` without changing persistence.
**Tech Stack:** Java 17, JavaFX, JUnit 5, Maven.
## Global Constraints
- Work only on `codex/plugin-ui-refactor` in the existing isolated worktree.
- Preserve generated HTML structure and history-file format.
- Do not introduce `new Thread`.
- File and directory choosers must not enter loading state.
- Local debug proxy is a visible option and defaults to disabled.
- Write and observe a failing test before production behavior.
---
### Task 1: Immutable API export request
**Files:**
- Create: `src/main/java/com/example/workutils/biz/apidoc/application/ApiDocExportRequest.java`
- Test: `src/test/java/com/example/workutils/biz/apidoc/application/ApiDocExportRequestTest.java`
**Interfaces:**
- Produces: `Mode { PATH, FQN, BATCH }`.
- Produces validated factories `path`, `fqn`, and `batch`.
- Carries debug-proxy opt-in and batch filters.
- [ ] Test mode-specific validation and immutable project-path copying.
- [ ] Observe focused test failure.
- [ ] Implement the minimal request record.
- [ ] Confirm focused test passes.
### Task 2: Explicit debug endpoint injection
**Files:**
- Modify: `src/main/java/com/example/workutils/biz/apidoc/generator/ApiDocGenerator.java`
- Test: `src/test/java/com/example/workutils/biz/apidoc/generator/ApiDocGeneratorProxyTest.java`
**Interfaces:**
- Produces constructor `ApiDocGenerator(WorkUtilsProperties, Supplier<String>)`.
- Existing constructor supplies an empty endpoint and never starts a proxy.
- [ ] Test that generated HTML receives the supplied endpoint and the default constructor does not instantiate the proxy.
- [ ] Observe focused test failure.
- [ ] Inject endpoint supplier and remove `DubboDebugProxy.getInstance()` from HTML rendering.
- [ ] Confirm focused test passes.
### Task 3: Single async export operation
**Files:**
- Create: `src/main/java/com/example/workutils/biz/apidoc/application/RunApiDocExportUseCase.java`
- Modify: `src/main/java/com/example/workutils/biz/apidoc/ApiDocWindow.java`
- Test: `src/test/java/com/example/workutils/biz/apidoc/application/RunApiDocExportUseCaseTest.java`
**Interfaces:**
- Consumes `ApiDocExportRequest`.
- Produces `Result(Path output, int moduleCount, int classCount, int methodCount)`.
- Reports progress through `OperationContext`.
- [ ] Test proxy-disabled execution does not resolve a local endpoint and proxy-enabled execution resolves it once.
- [ ] Observe focused test failure.
- [ ] Move export orchestration behind the use case and connect it to `AsyncOperationController`.
- [ ] Bind the button through `LoadingButtonBinder` and status through `OperationStatusPane`.
- [ ] Confirm focused and existing tests pass.
### Task 4: Shared history UI
**Files:**
- Modify: `src/main/java/com/example/workutils/biz/apidoc/ApiDocWindow.java`
- Test: `src/test/java/com/example/workutils/biz/apidoc/ApiDocHistoryMappingTest.java`
**Interfaces:**
- Produces package-visible `toHistoryItem(HistoryRow)`.
- Consumes `ExportHistoryPane` and `HistoryFileCommands`.
- [ ] Test history mapping.
- [ ] Observe focused test failure.
- [ ] Replace duplicated table and action cell.
- [ ] Confirm focused test passes.
### Task 5: Verification
- [ ] Run all tests.
- [ ] Compile with JDK 17.
- [ ] Run `git diff --check`.
- [ ] Confirm `ApiDocWindow` contains no `new Thread`, `buildActionCell`, or direct export-button state mutations.
- [ ] Inspect PATH, FQN, and BATCH panels without invoking export or file choosers.
@@ -0,0 +1,79 @@
# Diff Engine Phase 5 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:** Make CLI and JavaFX text comparison share one UI-independent line and character diff engine, then run expensive comparisons off the JavaFX thread.
**Architecture:** `DiffEngine` returns immutable line and character segment records without HTML or JavaFX dependencies. `DiffPlugin` delegates CLI comparison to it. `DiffWindow` renders those records and owns only presentation/navigation, with `AsyncOperationController` owning background execution.
**Tech Stack:** Java 17, JavaFX, JUnit 5, Maven.
## Global Constraints
- Preserve current unified text output and HTML colors/navigation.
- `DiffEngine` must not import JavaFX, HTML generators, filesystem, or plugin classes.
- CLI and UI must use the same line-diff implementation.
- Capture text on the UI thread before background execution.
- Test first.
---
### Task 1: Immutable line diff engine
**Files:**
- Create: `src/main/java/com/example/workutils/biz/diff/domain/DiffEngine.java`
- Test: `src/test/java/com/example/workutils/biz/diff/domain/DiffEngineLineTest.java`
**Interfaces:**
- Produces `DiffResult compareLines(List<String> left, List<String> right)`.
- Produces immutable `LineDiff`, `LineType`, and counts.
- [ ] Test unchanged, added, removed, replacement, and input-list mutation.
- [ ] Observe focused test failure.
- [ ] Implement LCS line comparison.
- [ ] Confirm focused test passes.
### Task 2: Structured character diff
**Files:**
- Modify: `src/main/java/com/example/workutils/biz/diff/domain/DiffEngine.java`
- Test: `src/test/java/com/example/workutils/biz/diff/domain/DiffEngineCharacterTest.java`
**Interfaces:**
- Produces `CharacterDiff compareCharacters(String left, String right)`.
- Produces immutable `CharacterSegment(Type, text)` lists for both sides.
- Produces `similarity(String left, String right)`.
- [ ] Test insertion, deletion, replacement, equality, HTML-like text, and similarity.
- [ ] Observe focused test failure.
- [ ] Implement character LCS and merged segments.
- [ ] Confirm focused test passes.
### Task 3: Migrate CLI and renderer
**Files:**
- Modify: `src/main/java/com/example/workutils/biz/diff/DiffPlugin.java`
- Modify: `src/main/java/com/example/workutils/biz/diff/DiffWindow.java`
- [ ] Replace `DiffPlugin` nested algorithm types with `DiffEngine`.
- [ ] Replace Window line/character computation with engine records.
- [ ] Delete duplicate LCS methods from Plugin and Window.
- [ ] Run focused engine and existing tests.
### Task 4: Background comparison state
**Files:**
- Modify: `src/main/java/com/example/workutils/biz/diff/DiffWindow.java`
- [ ] Add `AsyncOperationController<DiffRender>`.
- [ ] Bind compare button through `LoadingButtonBinder`.
- [ ] Add `OperationStatusPane`.
- [ ] Capture both text values before background execution.
- [ ] Apply HTML and counters only on success.
### Task 5: Verification
- [ ] Run all tests and compile with JDK 17.
- [ ] Run `git diff --check`.
- [ ] Confirm no LCS matrix implementation remains in `DiffPlugin` or `DiffWindow`.
- [ ] Inspect empty, equal, and different short-text UI states without saving files.
@@ -0,0 +1,90 @@
# Git Sync Refactor Phase 4 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:** Stop exposing credential-bearing Git URLs, make large project lists searchable, and give repository synchronization one structured async state owner.
**Architecture:** Convert raw remote URLs to safe display values before they enter table rows. Add a filtered view over immutable project presentation rows. Wrap the existing blocking synchronization API behind a use case that translates callbacks into immutable progress and result records consumed by `AsyncOperationController`.
**Tech Stack:** Java 17, JavaFX, JUnit 5, Maven.
## Global Constraints
- Preserve Git command behavior and damaged-repository confirmation flow.
- Never display username, password, or token-bearing URL text in the project table.
- Do not add raw `new Thread` to `GitSyncWindow`.
- Do not execute synchronization during UI verification.
- Test first for every new production behavior.
---
### Task 1: Remote URL safe display
**Files:**
- Create: `src/main/java/com/example/workutils/biz/git/presentation/RemoteUrlDisplay.java`
- Test: `src/test/java/com/example/workutils/biz/git/presentation/RemoteUrlDisplayTest.java`
- Modify: `src/main/java/com/example/workutils/biz/git/GitSyncWindow.java`
**Interfaces:**
- Produces `RemoteUrlDisplay.safe(String rawUrl)`.
- HTTPS credentials and SSH usernames are removed; host and repository path remain.
- [ ] Test HTTPS credentials, SSH SCP syntax, plain host/path, and blank values.
- [ ] Observe focused test failure.
- [ ] Implement safe display.
- [ ] Store only safe text in `ProjectRow`.
- [ ] Confirm focused test passes.
### Task 2: Project search and status filter
**Files:**
- Create: `src/main/java/com/example/workutils/biz/git/presentation/ProjectFilter.java`
- Test: `src/test/java/com/example/workutils/biz/git/presentation/ProjectFilterTest.java`
- Modify: `src/main/java/com/example/workutils/biz/git/GitSyncWindow.java`
**Interfaces:**
- Produces pure `matches(name, path, status, query, requiredStatus)`.
- [ ] Test case-insensitive name/path matching and status filtering.
- [ ] Observe focused test failure.
- [ ] Add search field, status combo, filtered list, and visible count.
- [ ] Confirm focused test passes.
### Task 3: Structured synchronization use case
**Files:**
- Create: `src/main/java/com/example/workutils/biz/git/application/GitSyncRequest.java`
- Create: `src/main/java/com/example/workutils/biz/git/application/GitSyncProgress.java`
- Create: `src/main/java/com/example/workutils/biz/git/application/RunGitSyncUseCase.java`
- Test: `src/test/java/com/example/workutils/biz/git/application/RunGitSyncUseCaseTest.java`
**Interfaces:**
- Consumes `Gateway.sync(branch, Listener)`.
- Produces immutable `Result(total, success, damaged, failed, elapsedSeconds, damagedRepos)`.
- Reports progress through `OperationContext`.
- [ ] Test request validation, progress translation, result construction, and missing completion failure.
- [ ] Observe focused test failure.
- [ ] Implement request, progress, gateway, listener, and result.
- [ ] Confirm focused test passes.
### Task 4: Migrate main sync action
**Files:**
- Modify: `src/main/java/com/example/workutils/biz/git/GitSyncWindow.java`
**Interfaces:**
- Consumes `AsyncOperationController<RunGitSyncUseCase.Result>`.
- Uses `LoadingButtonBinder` and `OperationStatusPane`.
- [ ] Replace manual button/progress state and window-level worker thread.
- [ ] Keep cancellation wired to both operation state and `plugin.cancelSync()`.
- [ ] Render structured completion summary and damaged rows.
- [ ] Confirm `GitSyncWindow` has no `new Thread` in synchronization flow.
### Task 5: Verification
- [ ] Run all tests and JDK 17 compilation.
- [ ] Run `git diff --check`.
- [ ] Inspect project list search/filter, masked remote values, and idle sync state.
- [ ] Do not start synchronization or damaged-repository actions.
@@ -0,0 +1,78 @@
# MyBatis Generator Phase 6 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:** Remove sensitive database details from the page, prevent database query/generation from freezing JavaFX, and replace the embedded-page “cancel” action with reset.
**Architecture:** Convert raw JDBC configuration into a safe `DatasourceSummary` before rendering. Capture query and generation inputs into immutable requests and execute two independent use cases through separate `AsyncOperationController` instances. Keep existing service callbacks as infrastructure adapters.
**Tech Stack:** Java 17, JavaFX, JUnit 5, Maven.
## Global Constraints
- Never expose database password, username, JDBC query parameters, or raw URL in page labels.
- Query and generate actions must have independent state.
- Preserve database access and generated artifact behavior.
- Directory choosers remain synchronous and do not show loading.
- UI verification must not query a database or generate files.
---
### Task 1: Safe datasource summary
**Files:**
- Create: `src/main/java/com/example/workutils/biz/mybatisgenerator/presentation/DatasourceSummary.java`
- Test: `src/test/java/com/example/workutils/biz/mybatisgenerator/presentation/DatasourceSummaryTest.java`
- Modify: `src/main/java/com/example/workutils/biz/mybatisgenerator/MyBatisGeneratorWindow.java`
**Interfaces:**
- Produces `DatasourceSummary.from(alias, jdbcUrl)`.
- Exposes alias, database type, masked host, and database name only.
- [ ] Test MySQL, OceanBase, query parameters, malformed URL, and blank alias.
- [ ] Observe focused test failure.
- [ ] Implement safe parsing and display text.
- [ ] Replace raw URL/user labels.
- [ ] Confirm focused test passes.
### Task 2: Immutable operation requests
**Files:**
- Create: `src/main/java/com/example/workutils/biz/mybatisgenerator/application/QueryTablesRequest.java`
- Create: `src/main/java/com/example/workutils/biz/mybatisgenerator/application/GenerateArtifactsRequest.java`
- Test: `src/test/java/com/example/workutils/biz/mybatisgenerator/application/GeneratorRequestTest.java`
- [ ] Test trimming, required pattern/table selection, directory defaults, and immutable table copying.
- [ ] Observe focused test failure.
- [ ] Implement both records.
- [ ] Confirm focused test passes.
### Task 3: Independent use cases and loading
**Files:**
- Create: `src/main/java/com/example/workutils/biz/mybatisgenerator/application/QueryTablesUseCase.java`
- Create: `src/main/java/com/example/workutils/biz/mybatisgenerator/application/GenerateArtifactsUseCase.java`
- Modify: `src/main/java/com/example/workutils/biz/mybatisgenerator/MyBatisGeneratorWindow.java`
- Test: `src/test/java/com/example/workutils/biz/mybatisgenerator/application/GeneratorUseCaseTest.java`
- [ ] Test gateway delegation and result immutability.
- [ ] Observe focused test failure.
- [ ] Add two use cases and two async controllers.
- [ ] Bind query/generate buttons independently and add operation status panes.
- [ ] Confirm focused tests pass.
### Task 4: Reset semantics
**Files:**
- Modify: `src/main/java/com/example/workutils/biz/mybatisgenerator/MyBatisGeneratorWindow.java`
- [ ] Rename bottom “取消” to “重置配置”.
- [ ] Reset pattern, table selection, and directory defaults without closing the page.
- [ ] Keep window close behavior only on actual window close.
### Task 5: Verification
- [ ] Run all tests and compile with JDK 17.
- [ ] Run `git diff --check`.
- [ ] Confirm Window no longer directly calls query/generate on the UI thread.
- [ ] Inspect datasource summary, idle query/generate states, and reset label without database or filesystem side effects.
@@ -0,0 +1,245 @@
# 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<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:
```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.
@@ -0,0 +1,108 @@
# Shared Result UI Phase 2 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:** Remove duplicated export-history and empty-result UI code from Scanner and MyBatis SQL Export while preserving their behavior.
**Architecture:** Add a small immutable adapter model between plugin-specific history rows and a reusable JavaFX history pane. Keep file-opening side effects behind injected callbacks so the shared pane only renders data and emits commands. Add a reusable empty-state component for later plugin migrations.
**Tech Stack:** Java 17, JavaFX, JUnit 5, Maven.
## Global Constraints
- Work only in `C:\tmp\workutils-plugin-ui-refactor` on branch `codex/plugin-ui-refactor`.
- Do not add raw `new Thread`.
- Shared UI components must not read files or launch external programs.
- Plugin-specific persistence formats must remain unchanged.
- Write and observe a failing test before each production behavior.
---
### Task 1: Immutable history presentation model
**Files:**
- Create: `src/main/java/com/example/workutils/system/ui/history/HistoryItem.java`
- Test: `src/test/java/com/example/workutils/system/ui/history/HistoryItemTest.java`
**Interfaces:**
- Produces: `HistoryItem(String index, String query, String filePath, String count, String timestamp)`.
- Produces: `fileDisplay(int maxLength)` for bounded single-line display.
- [ ] Write tests proving short paths remain unchanged, long paths are ellipsized while retaining the filename, and constructor values cannot be null.
- [ ] Run `.\mvnw.cmd -q -Dtest=HistoryItemTest test` and observe compilation failure because `HistoryItem` is absent.
- [ ] Implement the immutable record and its display method.
- [ ] Run the focused test and confirm it passes.
### Task 2: Reusable empty state
**Files:**
- Create: `src/main/java/com/example/workutils/system/ui/EmptyStatePane.java`
- Modify: `src/main/resources/css/light-theme.css`
- Test: `src/test/java/com/example/workutils/system/ui/EmptyStateContentTest.java`
**Interfaces:**
- Produces: `EmptyStatePane.Content(String title, String description, String actionLabel)`.
- Produces: `EmptyStatePane(Content content, Runnable action, int fontSize)`.
- [ ] Write pure content-validation tests proving title is required and a blank action label is allowed only when no action is rendered.
- [ ] Run the focused test and observe failure because the component is absent.
- [ ] Implement the content model, pane, optional action button, and theme classes.
- [ ] Run the focused test and confirm it passes.
### Task 3: Reusable export history pane
**Files:**
- Create: `src/main/java/com/example/workutils/system/ui/history/HistoryFileCommands.java`
- Create: `src/main/java/com/example/workutils/system/ui/history/ExportHistoryPane.java`
- Test: `src/test/java/com/example/workutils/system/ui/history/HistoryFileCommandsTest.java`
**Interfaces:**
- Consumes: `ObservableList<HistoryItem>`.
- Consumes: `HistoryFileCommands` callbacks for open file, open editor, open folder, and copy path.
- Produces: `ExportHistoryPane(String countColumnTitle, ObservableList<HistoryItem> items, HistoryFileCommands commands, int fontSize)`.
- [ ] Write tests proving `HistoryFileCommands` rejects missing callbacks and routes each command to the supplied consumer.
- [ ] Run the focused test and observe failure because the type is absent.
- [ ] Implement callback validation and the shared table, action cell, tooltip, context menu, and empty-state placeholder.
- [ ] Run the focused test and confirm it passes.
### Task 4: Migrate Scanner
**Files:**
- Modify: `src/main/java/com/example/workutils/biz/scanner/ScannerWindow.java`
- Test: `src/test/java/com/example/workutils/biz/scanner/ScannerHistoryMappingTest.java`
**Interfaces:**
- Consumes: `HistoryItem`, `ExportHistoryPane`, `HistoryFileCommands`.
- Produces: package-visible `toHistoryItem(HistoryRow row)` for deterministic presentation mapping.
- [ ] Write a mapping test for index, query, path, match count, and timestamp.
- [ ] Run the focused test and observe failure because the mapping method is absent.
- [ ] Replace duplicated table/action-cell construction with `ExportHistoryPane`.
- [ ] Run scanner tests and confirm they pass.
### Task 5: Migrate MyBatis SQL Export
**Files:**
- Modify: `src/main/java/com/example/workutils/biz/mybatisexport/MyBatisExportWindow.java`
- Test: `src/test/java/com/example/workutils/biz/mybatisexport/MyBatisExportHistoryMappingTest.java`
**Interfaces:**
- Consumes: `HistoryItem`, `ExportHistoryPane`, `HistoryFileCommands`.
- Produces: package-visible `toHistoryItem(HistoryRow row)`.
- [ ] Write a mapping test for index, query, path, export count, and timestamp.
- [ ] Run the focused test and observe failure because the mapping method is absent.
- [ ] Replace duplicated table/action-cell construction with `ExportHistoryPane`.
- [ ] Run export tests and confirm they pass.
### Task 6: Verification
**Files:**
- Modify only files required by failures found during verification.
- [ ] Run `.\mvnw.cmd -q test`.
- [ ] Run `.\mvnw.cmd -q -DskipTests compile`.
- [ ] Run `git diff --check`.
- [ ] Confirm Scanner and MyBatis SQL Export no longer declare `buildActionCell`.
- [ ] Inspect both pages without triggering scan, export, file-open, or delete side effects.