规划公共 UI 基础能力收尾
This commit is contained in:
@@ -0,0 +1,317 @@
|
||||
# Public UI Foundation 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:** Consolidate notifications, history-file actions, progress semantics, and accessibility behavior without changing plugin business results.
|
||||
|
||||
**Architecture:** Windows depend on narrow UI services instead of JavaFX dialogs, clipboard APIs, or operating-system helpers. Domain-specific progress records remain independent; shared panes own reusable accessibility behavior. Each migration is protected by unit or source-architecture tests before window code changes.
|
||||
|
||||
**Tech Stack:** Java 17, JavaFX, JUnit 5, Maven Wrapper, PowerShell, existing `AsyncOperationController` and `SystemOpenHelper`.
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- Do not modify Git Review, Mobile UI Automation, Settings, or Plugin Management.
|
||||
- Do not change generated output formats or persisted history formats.
|
||||
- Do not expose datasource passwords, tokens, or credential-bearing URLs in notifications.
|
||||
- Keep MyBatis SQL Export cancellable; it cooperatively checks cancellation between projects.
|
||||
- Scanner remains non-cancellable.
|
||||
- Use `$env:JAVA_HOME='C:\Program Files\Java\jdk-17'` for Maven commands.
|
||||
- Preserve unrelated untracked files under `docs/audit`, `docs/audit/screenshots`, and `src/main/resources`.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Notification model and JavaFX presenter
|
||||
|
||||
**Files:**
|
||||
- Create: `src/main/java/com/example/workutils/system/ui/notification/NotificationLevel.java`
|
||||
- Create: `src/main/java/com/example/workutils/system/ui/notification/NotificationMessage.java`
|
||||
- Create: `src/main/java/com/example/workutils/system/ui/notification/NotificationCenter.java`
|
||||
- Create: `src/main/java/com/example/workutils/system/ui/notification/FxNotificationCenter.java`
|
||||
- Test: `src/test/java/com/example/workutils/system/ui/notification/NotificationMessageTest.java`
|
||||
- Test: `src/test/java/com/example/workutils/system/ui/notification/FxNotificationCenterArchitectureTest.java`
|
||||
|
||||
**Interfaces:**
|
||||
- Produces: `NotificationMessage(NotificationLevel level, String title, String message)`.
|
||||
- Produces: `NotificationCenter.info(String,String)`, `warning(String,String)`, `error(String,String)`, and `boolean confirm(String,String,String)`.
|
||||
- Produces: `new FxNotificationCenter(Window owner)`.
|
||||
|
||||
- [ ] **Step 1: Write failing notification value tests**
|
||||
|
||||
Test trimming/default title, rejection of null or blank message, and preservation of multiline content:
|
||||
|
||||
```java
|
||||
assertEquals("提示", new NotificationMessage(INFO, " ", " 完成 ").title());
|
||||
assertEquals("完成", new NotificationMessage(INFO, " ", " 完成 ").message());
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> new NotificationMessage(ERROR, "失败", " "));
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run the focused test and verify RED**
|
||||
|
||||
Run: `$env:JAVA_HOME='C:\Program Files\Java\jdk-17'; .\mvnw.cmd -q -Dtest=NotificationMessageTest test`
|
||||
|
||||
Expected: compilation failure because notification types do not exist.
|
||||
|
||||
- [ ] **Step 3: Implement the model and narrow interface**
|
||||
|
||||
Use this contract:
|
||||
|
||||
```java
|
||||
public interface NotificationCenter {
|
||||
void show(NotificationMessage message);
|
||||
boolean confirm(String title, String header, String message);
|
||||
default void info(String title, String message) { show(new NotificationMessage(INFO, title, message)); }
|
||||
default void warning(String title, String message) { show(new NotificationMessage(WARNING, title, message)); }
|
||||
default void error(String title, String message) { show(new NotificationMessage(ERROR, title, message)); }
|
||||
}
|
||||
```
|
||||
|
||||
`FxNotificationCenter.show` must call immediately on the FX thread and otherwise use `Platform.runLater`. `confirm` must throw `IllegalStateException("confirm must be called on the JavaFX Application Thread")` off the FX thread. Map levels to JavaFX `AlertType`; use an expandable, read-only `TextArea` for long or multiline messages.
|
||||
|
||||
- [ ] **Step 4: Add a source-architecture test for the threading contract**
|
||||
|
||||
Read `FxNotificationCenter.java` and assert it contains `Platform.isFxApplicationThread()`, `Platform.runLater`, `initOwner(owner)`, and the exact confirmation-thread error. This test avoids displaying a modal dialog in CI.
|
||||
|
||||
- [ ] **Step 5: Run focused tests and commit**
|
||||
|
||||
Run: `$env:JAVA_HOME='C:\Program Files\Java\jdk-17'; .\mvnw.cmd -q -Dtest=NotificationMessageTest,FxNotificationCenterArchitectureTest test`
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
Commit: `git commit -m "新增统一通知中心"`
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Migrate MyBatis Generator and Git Sync notifications
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/main/java/com/example/workutils/biz/mybatisgenerator/MyBatisGeneratorWindow.java`
|
||||
- Modify: `src/main/java/com/example/workutils/biz/git/GitSyncWindow.java`
|
||||
- Modify: `src/test/java/com/example/workutils/biz/mybatisgenerator/MyBatisGeneratorWindowArchitectureTest.java`
|
||||
- Create: `src/test/java/com/example/workutils/biz/git/GitSyncWindowArchitectureTest.java`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `FxNotificationCenter(Window)` and all `NotificationCenter` methods from Task 1.
|
||||
- Produces: both windows contain one `NotificationCenter` field and no direct `new Alert`.
|
||||
|
||||
- [ ] **Step 1: Tighten architecture tests before migration**
|
||||
|
||||
Assert each target source contains `NotificationCenter` and `FxNotificationCenter`, and does not contain `new Alert`, `showAlert(`, or MyBatis's private `showInfo(`. Also assert Git's destructive confirmation text still contains `不可撤销`.
|
||||
|
||||
- [ ] **Step 2: Run tests and verify RED**
|
||||
|
||||
Run: `$env:JAVA_HOME='C:\Program Files\Java\jdk-17'; .\mvnw.cmd -q -Dtest=MyBatisGeneratorWindowArchitectureTest,GitSyncWindowArchitectureTest test`
|
||||
|
||||
Expected: FAIL because both windows still construct alerts.
|
||||
|
||||
- [ ] **Step 3: Migrate MyBatis Generator**
|
||||
|
||||
Initialize `notificationCenter = new FxNotificationCenter(stage)` after the stage is available. Replace query/generation warnings with `warning`, generation results with `info`, and `confirmGeneration` with:
|
||||
|
||||
```java
|
||||
return notificationCenter.confirm(
|
||||
"确认生成",
|
||||
"即将为 " + plan.tables().size() + " 个表生成代码",
|
||||
buildGenerationSummary(plan));
|
||||
```
|
||||
|
||||
Delete the private dialog methods and their now-unused JavaFX imports.
|
||||
|
||||
- [ ] **Step 4: Migrate Git Sync without changing destructive behavior**
|
||||
|
||||
Replace the missing-URL warning and bulk-reclone confirmation. Preserve the existing snapshot, row removal, thread start, delete/clone order, and confirmation wording. Only call `recloneAll` continuation when `confirm(...)` returns true.
|
||||
|
||||
- [ ] **Step 5: Run focused tests and commit**
|
||||
|
||||
Run: `$env:JAVA_HOME='C:\Program Files\Java\jdk-17'; .\mvnw.cmd -q -Dtest=NotificationMessageTest,MyBatisGeneratorWindowArchitectureTest,GitSyncWindowArchitectureTest test`
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
Commit: `git commit -m "统一生成器与 Git 同步通知"`
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Shared result-file actions and three-window migration
|
||||
|
||||
**Files:**
|
||||
- Create: `src/main/java/com/example/workutils/system/ui/history/FileActionGateway.java`
|
||||
- Create: `src/main/java/com/example/workutils/system/ui/history/SystemFileActionGateway.java`
|
||||
- Create: `src/main/java/com/example/workutils/system/ui/history/ResultFileActions.java`
|
||||
- Test: `src/test/java/com/example/workutils/system/ui/history/ResultFileActionsTest.java`
|
||||
- Modify: `src/main/java/com/example/workutils/biz/apidoc/ApiDocWindow.java`
|
||||
- Modify: `src/main/java/com/example/workutils/biz/scanner/ScannerWindow.java`
|
||||
- Modify: `src/main/java/com/example/workutils/biz/mybatisexport/MyBatisExportWindow.java`
|
||||
- Modify: `src/test/java/com/example/workutils/biz/apidoc/ApiDocWindowArchitectureTest.java`
|
||||
- Create: `src/test/java/com/example/workutils/biz/scanner/ScannerWindowArchitectureTest.java`
|
||||
- Create: `src/test/java/com/example/workutils/biz/mybatisexport/MyBatisExportWindowArchitectureTest.java`
|
||||
|
||||
**Interfaces:**
|
||||
- Produces: `FileActionGateway.open(Path)`, `edit(Path)`, `reveal(Path)`, `copyPath(String)`.
|
||||
- Produces: `ResultFileActions(FileActionGateway, NotificationCenter, Consumer<String>)` and `HistoryFileCommands commands()`.
|
||||
- Consumes: `NotificationCenter` from Task 1 and existing `HistoryFileCommands`.
|
||||
|
||||
- [ ] **Step 1: Write failing behavior tests using `@TempDir`**
|
||||
|
||||
Use a recording gateway and notification fake. Cover open/edit/reveal delegation for an existing file; normalized absolute copy path; copying a missing path with a warning success message; rejection of blank paths; no gateway call for missing open targets; and gateway exception translated to `notificationCenter.error("文件操作失败", ...)`.
|
||||
|
||||
- [ ] **Step 2: Run the focused test and verify RED**
|
||||
|
||||
Run: `$env:JAVA_HOME='C:\Program Files\Java\jdk-17'; .\mvnw.cmd -q -Dtest=ResultFileActionsTest test`
|
||||
|
||||
Expected: compilation failure because the action classes do not exist.
|
||||
|
||||
- [ ] **Step 3: Implement gateway and action service**
|
||||
|
||||
Production mapping must be exactly:
|
||||
|
||||
```java
|
||||
open -> SystemOpenHelper.openFile(path)
|
||||
edit -> SystemOpenHelper.editFile(path)
|
||||
reveal -> SystemOpenHelper.revealInFileManager(path.toString())
|
||||
copyPath -> Clipboard.getSystemClipboard().setContent(content)
|
||||
```
|
||||
|
||||
`ResultFileActions.commands()` returns method references for the four existing `HistoryFileCommands` slots. Catch platform `RuntimeException` around gateway calls, but let constructor null errors and invalid program state fail immediately.
|
||||
|
||||
- [ ] **Step 4: Add RED architecture constraints for the three windows**
|
||||
|
||||
Assert all three sources contain `ResultFileActions` and do not contain `SystemOpenHelper`, `ClipboardContent`, `private void openFile`, `private void openWithNotepad`, or `private void openFolder`.
|
||||
|
||||
- [ ] **Step 5: Migrate one shared command instance per window**
|
||||
|
||||
Create the command bundle once during window construction, using `new SystemFileActionGateway()` and `new FxNotificationCenter(stage)`. Scanner keeps its `[INFO] 已复制路径` log consumer; API Doc and SQL Export use their existing feedback areas where available, otherwise `message -> {}`. Pass the shared commands to every `ExportHistoryPane`.
|
||||
|
||||
- [ ] **Step 6: Run focused tests and commit**
|
||||
|
||||
Run: `$env:JAVA_HOME='C:\Program Files\Java\jdk-17'; .\mvnw.cmd -q -Dtest=HistoryFileCommandsTest,ResultFileActionsTest,ApiDocWindowArchitectureTest,ScannerWindowArchitectureTest,MyBatisExportWindowArchitectureTest test`
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
Commit: `git commit -m "统一导出历史文件操作"`
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Progress contracts and cancellation proof
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/main/java/com/example/workutils/biz/scanner/application/ScanProgress.java`
|
||||
- Modify: `src/main/java/com/example/workutils/biz/scanner/application/RunScanUseCase.java`
|
||||
- Modify: `src/main/java/com/example/workutils/biz/mybatisexport/application/SqlExportProgress.java`
|
||||
- Modify: `src/main/java/com/example/workutils/biz/mybatisexport/application/RunSqlExportUseCase.java`
|
||||
- Create: `src/test/java/com/example/workutils/biz/scanner/application/ScanProgressTest.java`
|
||||
- Create: `src/test/java/com/example/workutils/biz/mybatisexport/application/SqlExportProgressTest.java`
|
||||
- Create: `src/test/java/com/example/workutils/biz/mybatisexport/application/RunSqlExportCancellationTest.java`
|
||||
- Modify: `src/main/java/com/example/workutils/biz/scanner/ScannerWindow.java`
|
||||
- Modify: `src/main/java/com/example/workutils/biz/mybatisexport/MyBatisExportWindow.java`
|
||||
|
||||
**Interfaces:**
|
||||
- Produces: validated existing record constructors and stable progress messages; no shared base interface.
|
||||
- Preserves: `SqlExportProgress.fraction()` and SQL export's cooperative cancellation callback.
|
||||
|
||||
- [ ] **Step 1: Write RED boundary tests**
|
||||
|
||||
For `ScanProgress`, accept `-1`, `0`, and `1`; reject blank message, NaN, values below `-1`, and values above `1`. For SQL progress, accept `(0,0,"准备",0)` and `(2,2,"project",5)`; reject negative fields, blank project, and `current > total`. Assert `message()` returns `current + "/" + total + " " + projectName`, and `fraction()` returns the expected boundary value.
|
||||
|
||||
- [ ] **Step 2: Prove SQL export cancellation propagation with a service fake**
|
||||
|
||||
Capture the callback passed to `exportWithCallback`, request cancellation on the `OperationContext`, and assert `callback.isCancelled()` becomes true. The test must not connect to a database or write output files.
|
||||
|
||||
- [ ] **Step 3: Run tests and verify RED**
|
||||
|
||||
Run: `$env:JAVA_HOME='C:\Program Files\Java\jdk-17'; .\mvnw.cmd -q -Dtest=ScanProgressTest,SqlExportProgressTest,RunSqlExportCancellationTest test`
|
||||
|
||||
Expected: DTO validation tests fail before compact constructors are added.
|
||||
|
||||
- [ ] **Step 4: Add compact-constructor validation and explicit UI flags**
|
||||
|
||||
Normalize record text with `trim()`. Make `RunScanUseCase` create `ScanProgress` values before reporting them, and make `RunSqlExportUseCase` report `SqlExportProgress.message()` and `fraction()` instead of rebuilding those values inline. Keep Scanner's `new OperationStatusPane(..., false)`. Make SQL Export's cancellability explicit with `new OperationStatusPane(..., true)` so future readers do not depend on the convenience constructor.
|
||||
|
||||
- [ ] **Step 5: Run focused tests and commit**
|
||||
|
||||
Run: `$env:JAVA_HOME='C:\Program Files\Java\jdk-17'; .\mvnw.cmd -q -Dtest=ScanProgressTest,SqlExportProgressTest,RunSqlExportCancellationTest test`
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
Commit: `git commit -m "明确进度与取消契约"`
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Accessibility behavior in shared panes
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/main/java/com/example/workutils/system/ui/history/ExportHistoryPane.java`
|
||||
- Modify: `src/main/java/com/example/workutils/system/ui/EmptyStatePane.java`
|
||||
- Modify: `src/main/java/com/example/workutils/system/ui/async/OperationStatusPane.java`
|
||||
- Create: `src/test/java/com/example/workutils/system/ui/SharedPaneAccessibilityArchitectureTest.java`
|
||||
- Modify: `src/test/java/com/example/workutils/system/ui/EmptyStateContentTest.java`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: existing constructors; no call-site signature changes.
|
||||
- Produces: consistent accessible text, focus traversal, keyboard open behavior, and no focusable cancel button for non-cancellable panes.
|
||||
|
||||
- [ ] **Step 1: Write RED source and value tests**
|
||||
|
||||
Assert the shared pane sources use `setAccessibleText`, `setFocusTraversable`, and key handling for `KeyCode.ENTER`. Assert `OperationStatusPane` conditionally adds the cancel button rather than always adding a hidden focusable control. Extend empty-state content tests to verify trimmed text used by the combined accessible label.
|
||||
|
||||
- [ ] **Step 2: Run tests and verify RED**
|
||||
|
||||
Run: `$env:JAVA_HOME='C:\Program Files\Java\jdk-17'; .\mvnw.cmd -q -Dtest=SharedPaneAccessibilityArchitectureTest,EmptyStateContentTest test`
|
||||
|
||||
Expected: FAIL because accessibility calls and keyboard behavior are absent.
|
||||
|
||||
- [ ] **Step 3: Implement minimal shared accessibility behavior**
|
||||
|
||||
Set descriptive accessible text on table action buttons, history file cells, empty-state container/action, progress bar, status label, and cancel button. Clear file-cell text, tooltip, accessible text, and row reference when empty. On Enter, open the selected history row only when the table itself owns the key event; do not intercept Space or context-menu keys. Add cancel button to children only when `cancellable` is true.
|
||||
|
||||
- [ ] **Step 4: Check component colors without broad theme changes**
|
||||
|
||||
Search the three component files for inline `-fx-text-fill`, hexadecimal colors, and `Color.web`. If none exist, record no code change. If found, replace only those values with existing `Theme.TEXT_PRIMARY`, `Theme.TEXT_SECONDARY`, or established semantic style classes.
|
||||
|
||||
- [ ] **Step 5: Run focused tests and commit**
|
||||
|
||||
Run: `$env:JAVA_HOME='C:\Program Files\Java\jdk-17'; .\mvnw.cmd -q -Dtest=SharedPaneAccessibilityArchitectureTest,EmptyStateContentTest,HistoryFileCommandsTest test`
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
Commit: `git commit -m "完善公共组件无障碍交互"`
|
||||
|
||||
---
|
||||
|
||||
### Task 6: Full verification and audit closeout
|
||||
|
||||
**Files:**
|
||||
- Modify: `docs/audit/plugin-refactor-next.md`
|
||||
- Test: all `src/test/java` tests.
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: all deliverables from Tasks 1-5.
|
||||
- Produces: verified P1 public-foundation completion record.
|
||||
|
||||
- [ ] **Step 1: Run all automated verification**
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
$env:JAVA_HOME='C:\Program Files\Java\jdk-17'
|
||||
.\mvnw.cmd -q test
|
||||
.\mvnw.cmd -q -DskipTests compile
|
||||
git diff --check
|
||||
```
|
||||
|
||||
Expected: all commands exit 0. Record the exact test count from Maven reports.
|
||||
|
||||
- [ ] **Step 2: Perform read-only UI smoke verification**
|
||||
|
||||
Launch the existing application without stopping unrelated Java processes. Verify API Doc, Scanner, and MyBatis SQL Export history panes render and expose their actions; verify MyBatis Generator confirmation/result and Git Sync confirmation can be opened and cancelled. Do not execute file opens, database exports, code generation, repository deletion, or clone operations.
|
||||
|
||||
- [ ] **Step 3: Update the audit todo**
|
||||
|
||||
Move completed P1 items to the completed section. Record that SQL Export cancellation was retained after source verification, and leave Git Review, Mobile UI Automation, and Settings explicitly deferred/excluded.
|
||||
|
||||
- [ ] **Step 4: Re-run documentation diff checks and commit**
|
||||
|
||||
Run: `git diff --check; git status --short`
|
||||
|
||||
Confirm only intended files are staged.
|
||||
|
||||
Commit: `git commit -m "完成公共基础能力收尾"`
|
||||
@@ -90,7 +90,7 @@ API 文档、Scanner、MyBatis SQL 导出删除各自的 `openFile`、`openWithN
|
||||
- `ScanProgress`:正文不可为空;进度必须是 `-1`(不确定)或 `[0, 1]`。
|
||||
- `SqlExportProgress`:`current`、`total`、`recordCount` 不得为负,`current` 不得大于 `total`,项目名不可为空;`fraction()` 保持在 `[0, 1]`。
|
||||
|
||||
DTO 只表达领域进度。窗口通过小型纯函数把 DTO 转换为 `OperationSnapshot` 所需的 message 和 fraction,转换函数独立测试。
|
||||
DTO 只表达领域进度,并提供展示所需的稳定 message/fraction。用例把 DTO 值报告给 `OperationContext`,窗口不再重复拼接进度文案;DTO 的边界和展示结果独立测试。
|
||||
|
||||
Scanner 没有中断协议,保持不显示停止按钮。MyBatis SQL 导出已经通过 `ExportCallback.isCancelled()` 在项目边界读取 `OperationContext.isCancellationRequested()`,属于真实的协作式取消,因此保留停止按钮。测试必须证明取消请求能传递到回调,且界面只在运行阶段显示停止按钮。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user