制定统一 UI P0-P3 实施计划
This commit is contained in:
@@ -0,0 +1,417 @@
|
|||||||
|
# WorkUtils Unified UI P0 Application Shell 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:** Deliver the unified design tokens, Feather icon system, collapsible light navigation, top-right-only settings entry, shared page headings, and consistent loading/status controls.
|
||||||
|
|
||||||
|
**Architecture:** Keep business plugins behind the existing `MainWindow.embedPlugin(...)` boundary while extracting application-shell state and semantic visuals into focused classes. `DesignTokens` and CSS own appearance, `AppIcon`/`IconFactory` own icon mapping, and `NavigationRail` owns expanded/collapsed rendering; `MainWindow` only composes these pieces and switches content.
|
||||||
|
|
||||||
|
**Tech Stack:** Java 17, JavaFX 17.0.15, AtlantaFX 2.0.1, Ikonli JavaFX/Feather 12.4.0, JUnit 5, Maven Wrapper.
|
||||||
|
|
||||||
|
## Global Constraints
|
||||||
|
|
||||||
|
- Preserve the Windows native title bar; do not implement macOS red/yellow/green controls or an undecorated stage.
|
||||||
|
- Expanded navigation width is exactly 214px; collapsed width is exactly 56px; header height is exactly 48px.
|
||||||
|
- Use body 14px, secondary 12.5–13px, page title 20px semibold, section title 15px semibold, and 40–44px list/table rows.
|
||||||
|
- “系统设置” appears only in the top-right application bar and never as a sidebar item.
|
||||||
|
- Use Ikonli Feather 12.4.0 as the only application icon pack; do not add Emoji, Unicode pictograms, handcrafted SVG, or a second icon pack.
|
||||||
|
- Preserve plugin routing, configuration formats, business operations, and the light-only theme.
|
||||||
|
- Git review and mobile UI automation receive shell/theme compatibility only; their structural refactors belong to neither P0 nor later visual phases.
|
||||||
|
- Follow TDD for every behavior change and keep each task independently committable.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
### Create
|
||||||
|
|
||||||
|
- `src/main/java/com/example/workutils/system/ui/DesignTokens.java` — semantic font families, sizes, spacing, dimensions, radius, and colors.
|
||||||
|
- `src/main/java/com/example/workutils/system/ui/icon/AppIcon.java` — application semantic icon names mapped only to Feather glyphs.
|
||||||
|
- `src/main/java/com/example/workutils/system/ui/icon/IconFactory.java` — creates consistently sized/styled `FontIcon` nodes.
|
||||||
|
- `src/main/java/com/example/workutils/system/ui/shell/NavigationRail.java` — navigation list, selected state, tooltips, and collapse behavior.
|
||||||
|
- `src/main/java/com/example/workutils/system/ui/shell/PageHeader.java` — semantic page title and optional description.
|
||||||
|
- `src/test/java/com/example/workutils/system/ui/DesignTokensTest.java`
|
||||||
|
- `src/test/java/com/example/workutils/system/ui/FxTestSupport.java` — shared JavaFX-thread test runner used by P0–P3 component tests.
|
||||||
|
- `src/test/java/com/example/workutils/system/ui/icon/IconFactoryTest.java`
|
||||||
|
- `src/test/java/com/example/workutils/system/ui/shell/NavigationRailTest.java`
|
||||||
|
- `src/test/java/com/example/workutils/system/ui/MainWindowArchitectureTest.java`
|
||||||
|
|
||||||
|
### Modify
|
||||||
|
|
||||||
|
- `pom.xml` — add Ikonli version and two dependencies.
|
||||||
|
- `src/main/java/com/example/workutils/system/ui/Theme.java` — compatibility facade over semantic tokens and CSS classes.
|
||||||
|
- `src/main/resources/css/light-theme.css` — replace dark navigation and arbitrary component values with the approved light tokens.
|
||||||
|
- `src/main/java/com/example/workutils/system/ui/MainWindow.java` — compose header, rail, content, and settings action.
|
||||||
|
- `src/main/java/com/example/workutils/WorkUtilsApplication.java` — exclude settings from navigation and provide the settings action to `MainWindow`.
|
||||||
|
- `src/main/java/com/example/workutils/system/ui/async/LoadingButtonBinder.java` — fixed-width busy rendering and accessible state.
|
||||||
|
- `src/main/java/com/example/workutils/system/ui/async/OperationStatusPane.java` — semantic sizing, percent text, and state pseudoclasses.
|
||||||
|
- `src/test/java/com/example/workutils/system/plugin/DefaultPluginConfigurationTest.java`
|
||||||
|
- `src/test/java/com/example/workutils/system/ui/async/LoadingButtonTextTest.java`
|
||||||
|
- `src/test/java/com/example/workutils/system/ui/SharedPaneAccessibilityTest.java`
|
||||||
|
|
||||||
|
## Task 1: Lock the icon dependency and semantic icon boundary
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `pom.xml`
|
||||||
|
- Create: `src/main/java/com/example/workutils/system/ui/icon/AppIcon.java`
|
||||||
|
- Create: `src/main/java/com/example/workutils/system/ui/icon/IconFactory.java`
|
||||||
|
- Create: `src/test/java/com/example/workutils/system/ui/icon/IconFactoryTest.java`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Produces: `enum AppIcon` with `Ikon glyph()` and `IconFactory.create(AppIcon, int): FontIcon`.
|
||||||
|
- Produces: `IconFactory.createButtonIcon(AppIcon, String): FontIcon`, whose node has accessible text.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write the failing icon contract test**
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Test
|
||||||
|
void mapsEverySemanticIconToFeatherAndCreatesAccessibleNodes() {
|
||||||
|
for (AppIcon icon : AppIcon.values()) {
|
||||||
|
assertInstanceOf(Feather.class, icon.glyph());
|
||||||
|
FontIcon node = IconFactory.createButtonIcon(icon, "系统设置");
|
||||||
|
assertEquals(16, node.getIconSize());
|
||||||
|
assertEquals("系统设置", node.getAccessibleText());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run the test and verify dependency/types are missing**
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q -Dtest=IconFactoryTest test`
|
||||||
|
|
||||||
|
Expected: FAIL because Ikonli, `AppIcon`, and `IconFactory` do not exist.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Add the exact Maven dependencies**
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<ikonli.version>12.4.0</ikonli.version>
|
||||||
|
...
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.kordamp.ikonli</groupId>
|
||||||
|
<artifactId>ikonli-javafx</artifactId>
|
||||||
|
<version>${ikonli.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.kordamp.ikonli</groupId>
|
||||||
|
<artifactId>ikonli-feather-pack</artifactId>
|
||||||
|
<version>${ikonli.version}</version>
|
||||||
|
</dependency>
|
||||||
|
```
|
||||||
|
|
||||||
|
Use the official Ikonli 12.4.0 JavaFX and Feather artifacts documented at `https://kordamp.org/ikonli/`.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Implement the semantic map and factory**
|
||||||
|
|
||||||
|
```java
|
||||||
|
public enum AppIcon {
|
||||||
|
MENU(Feather.MENU), SETTINGS(Feather.SETTINGS), CHEVRON_LEFT(Feather.CHEVRON_LEFT),
|
||||||
|
CHEVRON_RIGHT(Feather.CHEVRON_RIGHT), GIT_SYNC(Feather.REFRESH_CW),
|
||||||
|
GIT_REVIEW(Feather.GIT_BRANCH), DIFF(Feather.COLUMNS), SQL_EXPORT(Feather.DATABASE),
|
||||||
|
MYBATIS_GENERATOR(Feather.CODE), SCANNER(Feather.SEARCH), API_DOC(Feather.FILE_TEXT),
|
||||||
|
EXPORT(Feather.DOWNLOAD), CANCEL(Feather.X), APP(Feather.TOOL);
|
||||||
|
|
||||||
|
private final Feather glyph;
|
||||||
|
AppIcon(Feather glyph) { this.glyph = glyph; }
|
||||||
|
public Ikon glyph() { return glyph; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`IconFactory` must add style class `app-icon`, set size from the argument, and never accept raw icon literals.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Run the focused test**
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q -Dtest=IconFactoryTest test`
|
||||||
|
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
- [ ] **Step 6: Commit**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git add pom.xml src/main/java/com/example/workutils/system/ui/icon src/test/java/com/example/workutils/system/ui/icon
|
||||||
|
git commit -m "引入统一 Feather 图标系统"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Task 2: Introduce semantic tokens and remove arbitrary global typography
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `src/main/java/com/example/workutils/system/ui/DesignTokens.java`
|
||||||
|
- Create: `src/test/java/com/example/workutils/system/ui/DesignTokensTest.java`
|
||||||
|
- Modify: `src/main/java/com/example/workutils/system/ui/Theme.java`
|
||||||
|
- Modify: `src/main/resources/css/light-theme.css`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Produces: `DesignTokens.uiFontFamily(): String`, `monoFontFamily(): String`.
|
||||||
|
- Produces exact constants `FONT_PAGE_TITLE=20`, `FONT_SECTION_TITLE=15`, `FONT_BODY=14`, `FONT_SECONDARY=13`, `ROW_HEIGHT=40`, `CONTROL_HEIGHT=36`, `NAV_EXPANDED_WIDTH=214`, `NAV_COLLAPSED_WIDTH=56`, `HEADER_HEIGHT=48`.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write failing token and font fallback tests**
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Test
|
||||||
|
void exposesApprovedDimensionsAndSelectsFirstAvailableFont() {
|
||||||
|
assertEquals(214, DesignTokens.NAV_EXPANDED_WIDTH);
|
||||||
|
assertEquals(56, DesignTokens.NAV_COLLAPSED_WIDTH);
|
||||||
|
assertEquals(48, DesignTokens.HEADER_HEIGHT);
|
||||||
|
assertEquals("Segoe UI", DesignTokens.firstAvailable(
|
||||||
|
List.of("Missing Font", "Segoe UI"), List.of("Segoe UI")));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run the test and verify failure**
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q -Dtest=DesignTokensTest test`
|
||||||
|
|
||||||
|
Expected: FAIL because `DesignTokens` is missing.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Implement deterministic tokens and font lookup**
|
||||||
|
|
||||||
|
```java
|
||||||
|
static String firstAvailable(List<String> preferred, List<String> installed) {
|
||||||
|
return preferred.stream().filter(installed::contains).findFirst().orElse("System");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String uiFontFamily() {
|
||||||
|
return firstAvailable(List.of("Segoe UI Variable Text", "Segoe UI", "Microsoft YaHei UI"),
|
||||||
|
Font.getFamilies());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Define matching mono fallback: `JetBrains Mono`, `Cascadia Mono`, `Consolas`, then `Monospaced`.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Convert `Theme` to semantic font methods**
|
||||||
|
|
||||||
|
Add `pageTitle()`, `sectionTitle()`, `body()`, `secondary()`, `tableText()`, and `mono()` without size parameters. Keep current parameterized methods temporarily deprecated so P1–P3 can migrate callers without breaking compilation.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Replace root CSS variables and global control metrics**
|
||||||
|
|
||||||
|
Add `.page-title`, `.section-title`, `.body-text`, `.secondary-text`, `.app-icon`, `.control-default`, `.table-default`, `.project-tree`, and `.log-area` rules using the approved palette and heights. Remove dark nav colors, label shadows, ripple styles, and focus-ring suppression.
|
||||||
|
|
||||||
|
- [ ] **Step 6: Run tests and CSS contract checks**
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q -Dtest=DesignTokensTest,ApiDocWindowArchitectureTest test`
|
||||||
|
|
||||||
|
Expected: PASS and `.apidoc-tab-pane`, `.operation-status`, `.empty-state` remain present.
|
||||||
|
|
||||||
|
- [ ] **Step 7: Commit**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git add src/main/java/com/example/workutils/system/ui/DesignTokens.java src/main/java/com/example/workutils/system/ui/Theme.java src/main/resources/css/light-theme.css src/test/java/com/example/workutils/system/ui/DesignTokensTest.java
|
||||||
|
git commit -m "建立统一 UI 设计令牌"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Task 3: Build a testable collapsible navigation rail
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `src/main/java/com/example/workutils/system/ui/shell/NavigationRail.java`
|
||||||
|
- Create: `src/test/java/com/example/workutils/system/ui/shell/NavigationRailTest.java`
|
||||||
|
- Create: `src/test/java/com/example/workutils/system/ui/FxTestSupport.java`
|
||||||
|
- Modify: `src/main/java/com/example/workutils/system/ui/MainWindow.java`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: `List<MainWindow.NavItem>`, `Consumer<MainWindow.NavItem>`, `IconFactory`.
|
||||||
|
- Produces: `expandedProperty(): ReadOnlyBooleanProperty`, `setExpanded(boolean)`, `toggle()`, `selectPlugin(String)`, `selectedPluginId(): String`, and package-visible `tooltipFor(String): Tooltip` for component verification.
|
||||||
|
- Changes: `MainWindow.NavItem.icon` from `String` to `AppIcon`.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write failing JavaFX component tests**
|
||||||
|
|
||||||
|
First add this complete shared helper and statically import `fx` in component tests:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public final class FxTestSupport {
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
CountDownLatch started = new CountDownLatch(1);
|
||||||
|
Platform.startup(started::countDown);
|
||||||
|
started.await();
|
||||||
|
} catch (IllegalStateException alreadyStarted) {
|
||||||
|
// JavaFX toolkit is already running in this test JVM.
|
||||||
|
} catch (InterruptedException interrupted) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
throw new AssertionError(interrupted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T fx(Callable<T> action) throws Exception {
|
||||||
|
FutureTask<T> task = new FutureTask<>(action);
|
||||||
|
Platform.runLater(task);
|
||||||
|
return task.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void fx(ThrowingRunnable action) throws Exception {
|
||||||
|
fx(() -> { action.run(); return null; });
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface public interface ThrowingRunnable { void run() throws Exception; }
|
||||||
|
private FxTestSupport() {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```java
|
||||||
|
private static List<MainWindow.NavItem> items() {
|
||||||
|
return List.of(new MainWindow.NavItem(
|
||||||
|
AppIcon.API_DOC, "API 接口文档导出", "导出接口文档", () -> {}, "api-doc"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void collapseKeepsSelectionAndExposesTooltips() {
|
||||||
|
NavigationRail rail = fx(() -> new NavigationRail(items(), ignored -> {}));
|
||||||
|
fx(() -> rail.selectPlugin("api-doc"));
|
||||||
|
fx(rail::toggle);
|
||||||
|
assertFalse(rail.isExpanded());
|
||||||
|
assertEquals(56, rail.getPrefWidth());
|
||||||
|
assertEquals("api-doc", rail.selectedPluginId());
|
||||||
|
assertEquals("API 接口文档导出", rail.tooltipFor("api-doc").getText());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run the test and verify failure**
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q -Dtest=NavigationRailTest test`
|
||||||
|
|
||||||
|
Expected: FAIL because `NavigationRail` is missing.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Implement the rail without a draggable `SplitPane` divider**
|
||||||
|
|
||||||
|
Use a `VBox` with fixed min/pref/max width bound to `expandedProperty`. Render `FontIcon` plus label in expanded state and icon-only with Tooltip in collapsed state. Use CSS pseudoclass `collapsed`; do not retain the current ripple circle or hardcoded white text.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Integrate it into `MainWindow`**
|
||||||
|
|
||||||
|
Replace the shell-level `SplitPane` with `HBox(navigationRail, contentArea)`. Keep plugin content split panes untouched. Remove responsive font scaling; window width changes affect available layout space, not the global font size.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Run navigation and default plugin tests**
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q -Dtest=NavigationRailTest,DefaultPluginConfigurationTest test`
|
||||||
|
|
||||||
|
Expected: PASS; selection survives collapse and plugin order is unchanged.
|
||||||
|
|
||||||
|
- [ ] **Step 6: Commit**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git add src/main/java/com/example/workutils/system/ui/shell/NavigationRail.java src/main/java/com/example/workutils/system/ui/MainWindow.java src/test/java/com/example/workutils/system/ui/FxTestSupport.java src/test/java/com/example/workutils/system/ui/shell/NavigationRailTest.java
|
||||||
|
git commit -m "实现可折叠应用导航"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Task 4: Compose the approved header and unique settings entry
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `src/main/java/com/example/workutils/system/ui/shell/PageHeader.java`
|
||||||
|
- Modify: `src/main/java/com/example/workutils/system/ui/MainWindow.java`
|
||||||
|
- Modify: `src/main/java/com/example/workutils/WorkUtilsApplication.java`
|
||||||
|
- Modify: `src/test/java/com/example/workutils/system/plugin/DefaultPluginConfigurationTest.java`
|
||||||
|
- Create: `src/test/java/com/example/workutils/system/ui/MainWindowArchitectureTest.java`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Changes constructor to `MainWindow(List<NavItem>, WorkUtilsProperties, Runnable openSettings)`.
|
||||||
|
- Produces one header settings button whose action is exactly `openSettings`.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add failing source and navigation contract tests**
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Test
|
||||||
|
void settingsIsApplicationActionNotNavigationItem() throws IOException {
|
||||||
|
String app = Files.readString(Path.of("src/main/java/com/example/workutils/WorkUtilsApplication.java"));
|
||||||
|
String shell = Files.readString(Path.of("src/main/java/com/example/workutils/system/ui/MainWindow.java"));
|
||||||
|
assertFalse(app.contains("new MainWindow.NavItem(\n \"⚙️\", \"系统设置\""));
|
||||||
|
assertTrue(shell.contains("AppIcon.SETTINGS"));
|
||||||
|
assertTrue(shell.contains("openSettings.run()"));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run the test and verify it fails**
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q -Dtest=MainWindowArchitectureTest,DefaultPluginConfigurationTest test`
|
||||||
|
|
||||||
|
Expected: FAIL because settings remains in `buildNavItems()`.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Implement the approved header hierarchy**
|
||||||
|
|
||||||
|
Build: menu toggle, app icon/name, chevron, current page title, spacer, settings button. The stage remains decorated. `PageHeader` provides title/description for plugin pages but the shell header must not be duplicated inside content.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Remove the sidebar settings item and wire the action**
|
||||||
|
|
||||||
|
Pass `this::openSettingsEmbedded` into `MainWindow`. Keep `openSettingsEmbedded()` and cached `SettingsWindow` behavior unchanged. When settings opens, the top title becomes “系统设置” while the sidebar selection is cleared or remains on the previous plugin without adding a settings row.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Run focused tests**
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q -Dtest=MainWindowArchitectureTest,DefaultPluginConfigurationTest test`
|
||||||
|
|
||||||
|
Expected: PASS with exactly seven normal navigation functions and no sidebar settings entry.
|
||||||
|
|
||||||
|
- [ ] **Step 6: Commit**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git add src/main/java/com/example/workutils/system/ui/shell/PageHeader.java src/main/java/com/example/workutils/system/ui/MainWindow.java src/main/java/com/example/workutils/WorkUtilsApplication.java src/test/java/com/example/workutils/system/ui/MainWindowArchitectureTest.java src/test/java/com/example/workutils/system/plugin/DefaultPluginConfigurationTest.java
|
||||||
|
git commit -m "统一应用顶部栏与设置入口"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Task 5: Normalize loading buttons and operation status
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/main/java/com/example/workutils/system/ui/async/LoadingButtonBinder.java`
|
||||||
|
- Modify: `src/main/java/com/example/workutils/system/ui/async/OperationStatusPane.java`
|
||||||
|
- Modify: `src/test/java/com/example/workutils/system/ui/async/LoadingButtonTextTest.java`
|
||||||
|
- Modify: `src/test/java/com/example/workutils/system/ui/SharedPaneAccessibilityTest.java`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Preserve: `LoadingButtonBinder.bind(Button, AsyncOperationController<?>, String)`.
|
||||||
|
- Add: `OperationStatusPane(AsyncOperationController<?>, boolean cancellable)`; deprecate font-size constructors.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add failing state/accessibility tests**
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Test
|
||||||
|
void busyButtonKeepsWidthAndAnnouncesState() {
|
||||||
|
Button button = fx(() -> new Button("导出文档"));
|
||||||
|
AsyncOperationController<Void> controller = new AsyncOperationController<>(ignored -> {}, Runnable::run);
|
||||||
|
fx(() -> LoadingButtonBinder.bind(button, controller, "导出文档"));
|
||||||
|
double idleMinWidth = fx(button::getMinWidth);
|
||||||
|
fx(() -> controller.start("导出中", context -> null, ignored -> {}));
|
||||||
|
assertEquals("导出中…", fx(button::getText));
|
||||||
|
assertEquals("导出中…", fx(button::getAccessibleText));
|
||||||
|
assertTrue(fx(button::getMinWidth) >= idleMinWidth);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run focused tests and verify failure**
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q -Dtest=LoadingButtonTextTest,SharedPaneAccessibilityTest test`
|
||||||
|
|
||||||
|
Expected: FAIL on width/accessibility and the font-size-free constructor.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Implement semantic rendering**
|
||||||
|
|
||||||
|
Before binding, measure/preserve the idle button minimum width. Busy state uses the shared 14px spinner, CSS pseudoclass `running`, and accessible text matching the visible text. `OperationStatusPane` adds percent text for determinate progress and only creates a focusable cancel button when cancellation is real.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run focused and full P0 tests**
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q -Dtest=LoadingButtonTextTest,SharedPaneAccessibilityTest,NavigationRailTest,MainWindowArchitectureTest,DesignTokensTest,IconFactoryTest test`
|
||||||
|
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Run compile and diff checks**
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q -DskipTests compile`
|
||||||
|
|
||||||
|
Run: `git diff --check`
|
||||||
|
|
||||||
|
Expected: both exit 0.
|
||||||
|
|
||||||
|
- [ ] **Step 6: Perform read-only visual acceptance**
|
||||||
|
|
||||||
|
Launch from the IDE or existing approved run configuration. Capture welcome, expanded sidebar, collapsed sidebar, and one running task at 1440px and 1024px widths. Verify native Windows controls, one top-right settings entry, stable typography, and no clipped navigation. Do not trigger export, Git sync, or file operations.
|
||||||
|
|
||||||
|
- [ ] **Step 7: Commit**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git add src/main/java/com/example/workutils/system/ui/async src/test/java/com/example/workutils/system/ui/async src/test/java/com/example/workutils/system/ui/SharedPaneAccessibilityTest.java
|
||||||
|
git commit -m "统一异步操作视觉状态"
|
||||||
|
```
|
||||||
|
|
||||||
|
## P0 Completion Gate
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q test`
|
||||||
|
|
||||||
|
Expected: all tests pass with zero failures and errors.
|
||||||
|
|
||||||
|
Run: `git diff --check`
|
||||||
|
|
||||||
|
Expected: exit 0. Do not start P1 until the expanded/collapsed shell screenshots have been compared with `docs/superpowers/specs/assets/2026-08-01-workutils-unified-ui-reference.png` and accepted.
|
||||||
@@ -0,0 +1,259 @@
|
|||||||
|
# WorkUtils Unified UI P1 API Document Export 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:** Recompose the API document export plugin to match the approved segmented-mode form, honest progress/action row, and resizable execution log while preserving requests, proxy behavior, history, and base-class configuration.
|
||||||
|
|
||||||
|
**Architecture:** Keep `ApiDocExportRequest`, `RunApiDocExportUseCase`, `AsyncOperationController`, and `ExportHistoryPane` unchanged. Extract presentation-only controls from the 900-line `ApiDocWindow`: `ApiDocModeSelector` renders modes, `ApiDocExportForm` owns mode-specific inputs, and `ResizableLogPane` owns log layout; `ApiDocWindow` remains the composition and event-wiring boundary.
|
||||||
|
|
||||||
|
**Tech Stack:** Java 17, JavaFX 17.0.15, AtlantaFX 2.0.1, P0 `DesignTokens`/`IconFactory`, JUnit 5, Maven Wrapper.
|
||||||
|
|
||||||
|
## Global Constraints
|
||||||
|
|
||||||
|
- Preserve tabs exactly: “导出配置 / 导出历史 / 基类配置”.
|
||||||
|
- Preserve all three request modes and the explicit local-proxy checkbox; do not change request defaults or generated output.
|
||||||
|
- Use one segmented mode row instead of three oversized cards.
|
||||||
|
- Running state shows determinate progress when available, “导出中…”, and “取消” only when the operation has real cancellation support.
|
||||||
|
- Navigation and history viewing remain usable during export; only request-mutating inputs are disabled.
|
||||||
|
- The log pane is collapsible and vertically resizable, defaults to 10–14 visible lines, and uses the P0 mono token.
|
||||||
|
- Do not expose credentials, tokens, or credential-bearing URLs in logs.
|
||||||
|
- Follow TDD and preserve `LoadingButtonBinder` as the only loading-button renderer.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
### Create
|
||||||
|
|
||||||
|
- `src/main/java/com/example/workutils/biz/apidoc/presentation/ApiDocModeSelector.java`
|
||||||
|
- `src/main/java/com/example/workutils/biz/apidoc/presentation/ApiDocExportForm.java`
|
||||||
|
- `src/main/java/com/example/workutils/system/ui/ResizableLogPane.java`
|
||||||
|
- `src/test/java/com/example/workutils/biz/apidoc/presentation/ApiDocModeSelectorTest.java`
|
||||||
|
- `src/test/java/com/example/workutils/biz/apidoc/presentation/ApiDocExportFormTest.java`
|
||||||
|
- `src/test/java/com/example/workutils/system/ui/ResizableLogPaneTest.java`
|
||||||
|
|
||||||
|
### Modify
|
||||||
|
|
||||||
|
- `src/main/java/com/example/workutils/biz/apidoc/ApiDocWindow.java`
|
||||||
|
- `src/main/resources/css/light-theme.css`
|
||||||
|
- `src/test/java/com/example/workutils/biz/apidoc/ApiDocWindowArchitectureTest.java`
|
||||||
|
- `src/test/java/com/example/workutils/biz/apidoc/ApiDocHistoryMappingTest.java`
|
||||||
|
|
||||||
|
## Task 1: Extract the segmented mode selector
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `src/main/java/com/example/workutils/biz/apidoc/presentation/ApiDocModeSelector.java`
|
||||||
|
- Create: `src/test/java/com/example/workutils/biz/apidoc/presentation/ApiDocModeSelectorTest.java`
|
||||||
|
- Modify: `src/main/resources/css/light-theme.css`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Produces: `selectedModeProperty(): ObjectProperty<ApiDocExportRequest.Mode>`.
|
||||||
|
- Produces: `setMode(Mode)`, `getMode()`, and exactly three accessible toggle segments.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write the failing selector test**
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Test
|
||||||
|
void exposesThreeModesAndUpdatesOneSelectedProperty() {
|
||||||
|
ApiDocModeSelector selector = fx(ApiDocModeSelector::new);
|
||||||
|
assertEquals(List.of(Mode.PATH, Mode.FQN, Mode.BATCH), selector.modes());
|
||||||
|
fx(() -> selector.setMode(Mode.FQN));
|
||||||
|
assertEquals(Mode.FQN, fx(selector::getMode));
|
||||||
|
assertEquals(1, fx(selector::selectedCount));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run and verify failure**
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q -Dtest=ApiDocModeSelectorTest test`
|
||||||
|
|
||||||
|
Expected: FAIL because the component is missing.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Implement the selector**
|
||||||
|
|
||||||
|
Use a `ToggleGroup` and three `ToggleButton`s. Each segment uses P0 `AppIcon` values, visible title/helper labels, CSS class `segmented-mode`, and an accessible label containing both title and helper text. Prevent clearing the final selection.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run test and commit**
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q -Dtest=ApiDocModeSelectorTest test`
|
||||||
|
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git add src/main/java/com/example/workutils/biz/apidoc/presentation/ApiDocModeSelector.java src/test/java/com/example/workutils/biz/apidoc/presentation/ApiDocModeSelectorTest.java src/main/resources/css/light-theme.css
|
||||||
|
git commit -m "重构 API 导出模式选择器"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Task 2: Extract mode-specific form state without changing requests
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `src/main/java/com/example/workutils/biz/apidoc/presentation/ApiDocExportForm.java`
|
||||||
|
- Create: `src/test/java/com/example/workutils/biz/apidoc/presentation/ApiDocExportFormTest.java`
|
||||||
|
- Modify: `src/main/java/com/example/workutils/biz/apidoc/ApiDocWindow.java`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: `ApiDocModeSelector`, existing project/module data, and proxy default.
|
||||||
|
- Produces: `buildRequest(): ApiDocExportRequest`, `requestControls(): List<Control>`, `setRequestControlsDisabled(boolean)`.
|
||||||
|
- Produces testable setters `setPathInput(String)`, `setFqnInput(String)`, `setBatchProjectPaths(List<String>)`, and `setDebugProxyEnabled(boolean)`; production event handlers use the same properties.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write failing request-equivalence tests**
|
||||||
|
|
||||||
|
```java
|
||||||
|
private static ApiDocExportForm fixtureForm(ApiDocExportRequest.Mode mode) {
|
||||||
|
ApiDocModeSelector selector = new ApiDocModeSelector();
|
||||||
|
selector.setMode(mode);
|
||||||
|
ApiDocExportForm form = new ApiDocExportForm(selector, List.of("D:/demo"), false);
|
||||||
|
form.setPathInput("D:/demo/OrderFacade.java");
|
||||||
|
form.setFqnInput("com.example.OrderFacade");
|
||||||
|
form.setBatchProjectPaths(List.of("D:/demo"));
|
||||||
|
form.setDebugProxyEnabled(true);
|
||||||
|
return form;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@EnumSource(ApiDocExportRequest.Mode.class)
|
||||||
|
void buildsTheSameRequestFieldsForEveryMode(ApiDocExportRequest.Mode mode) {
|
||||||
|
ApiDocExportForm form = fx(() -> fixtureForm(mode));
|
||||||
|
ApiDocExportRequest request = fx(form::buildRequest);
|
||||||
|
assertEquals(mode, request.mode());
|
||||||
|
switch (mode) {
|
||||||
|
case PATH -> assertEquals("D:/demo/OrderFacade.java", request.input());
|
||||||
|
case FQN -> assertEquals("com.example.OrderFacade", request.input());
|
||||||
|
case BATCH -> assertEquals(List.of("D:/demo"), request.projectPaths());
|
||||||
|
}
|
||||||
|
assertTrue(request.debugProxyEnabled());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run and verify failure**
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q -Dtest=ApiDocExportFormTest test`
|
||||||
|
|
||||||
|
Expected: FAIL because `ApiDocExportForm` is missing.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Move presentation and request extraction**
|
||||||
|
|
||||||
|
Move the current custom path, FQN, batch project/module inputs, and validation messages into the form. Preserve the existing `ApiDocExportRequest` constructor/factory calls verbatim. Render label above input and helper below input; keep proxy as an explicit checkbox with helper text.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Delegate `ApiDocWindow.buildRequest()` to the form**
|
||||||
|
|
||||||
|
`ApiDocWindow` may catch/display `IOException`, but it must no longer inspect individual mode controls. Bind selector mode to the form’s visible managed section.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Run request/use-case tests**
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q -Dtest=ApiDocExportFormTest,ApiDocExportRequestTest,RunApiDocExportUseCaseTest,ApiDocGeneratorProxyTest test`
|
||||||
|
|
||||||
|
Expected: PASS with unchanged proxy and generation semantics.
|
||||||
|
|
||||||
|
- [ ] **Step 6: Commit**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git add src/main/java/com/example/workutils/biz/apidoc/presentation/ApiDocExportForm.java src/main/java/com/example/workutils/biz/apidoc/ApiDocWindow.java src/test/java/com/example/workutils/biz/apidoc/presentation/ApiDocExportFormTest.java
|
||||||
|
git commit -m "拆分 API 导出表单状态"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Task 3: Add the approved resizable log pane
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `src/main/java/com/example/workutils/system/ui/ResizableLogPane.java`
|
||||||
|
- Create: `src/test/java/com/example/workutils/system/ui/ResizableLogPaneTest.java`
|
||||||
|
- Modify: `src/main/resources/css/light-theme.css`
|
||||||
|
- Modify: `src/main/java/com/example/workutils/biz/apidoc/ApiDocWindow.java`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Produces: `append(String)`, `clear()`, `expandedProperty()`, `setExpanded(boolean)`, `lineCountProperty()`, and package-visible `isBodyManaged()` for component verification.
|
||||||
|
- Exposes a read-only `TextArea`; default preferred height must fit 12 mono lines.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write failing component tests**
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Test
|
||||||
|
void countsLinesAndCollapseRemovesBodyFromLayout() {
|
||||||
|
ResizableLogPane pane = fx(() -> new ResizableLogPane("执行日志", 12));
|
||||||
|
fx(() -> pane.append("one\ntwo"));
|
||||||
|
assertEquals(2, fx(() -> pane.lineCountProperty().get()));
|
||||||
|
fx(() -> pane.setExpanded(false));
|
||||||
|
assertFalse(fx(pane::isBodyManaged));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run and verify failure**
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q -Dtest=ResizableLogPaneTest test`
|
||||||
|
|
||||||
|
Expected: FAIL because the component is missing.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Implement and integrate**
|
||||||
|
|
||||||
|
Use a header, read-only text area, footer line count, collapse control, and resize grip/split divider that never overlays log text. Replace direct `logArea.appendText` calls with `logPane.append`; keep redaction at the existing message-producing boundary.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run tests and commit**
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q -Dtest=ResizableLogPaneTest,ApiDocWindowArchitectureTest test`
|
||||||
|
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git add src/main/java/com/example/workutils/system/ui/ResizableLogPane.java src/test/java/com/example/workutils/system/ui/ResizableLogPaneTest.java src/main/java/com/example/workutils/biz/apidoc/ApiDocWindow.java src/main/resources/css/light-theme.css
|
||||||
|
git commit -m "增加可调整执行日志面板"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Task 4: Recompose the API page and synchronize operation state
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/main/java/com/example/workutils/biz/apidoc/ApiDocWindow.java`
|
||||||
|
- Modify: `src/test/java/com/example/workutils/biz/apidoc/ApiDocWindowArchitectureTest.java`
|
||||||
|
- Modify: `src/test/java/com/example/workutils/biz/apidoc/ApiDocHistoryMappingTest.java`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: `ApiDocModeSelector`, `ApiDocExportForm`, `OperationStatusPane`, `ResizableLogPane`.
|
||||||
|
- Preserves: `getContentPane()`, `toHistoryItem(HistoryRow)`, all three tabs, and history persistence.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Strengthen the failing architecture test**
|
||||||
|
|
||||||
|
```java
|
||||||
|
assertTrue(source.contains("new ApiDocModeSelector()"));
|
||||||
|
assertTrue(source.contains("new ApiDocExportForm("));
|
||||||
|
assertTrue(source.contains("new ResizableLogPane(\"执行日志\", 12)"));
|
||||||
|
assertFalse(source.contains("buildModeCard("));
|
||||||
|
assertFalse(source.contains("setGraphic(btnBox)"));
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run and verify failure**
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q -Dtest=ApiDocWindowArchitectureTest test`
|
||||||
|
|
||||||
|
Expected: FAIL on the old card/action-cell implementation.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Build the final layout**
|
||||||
|
|
||||||
|
Compose tabs, selector, form, a progress/action row, and log pane in that order. Use `ExportHistoryPane` for history and keep base-class configuration content unchanged. Remove the duplicated history action cell code only after mapping tests prove shared history equivalence.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Bind disablement narrowly**
|
||||||
|
|
||||||
|
Bind `form.setRequestControlsDisabled(snapshot.isBusy())`; leave tabs, navigation, and history controls enabled. Bind the export button through `LoadingButtonBinder` and show cancel only if `RunApiDocExportUseCase` cancellation reaches its operation context; otherwise omit cancel rather than simulate it.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Run all API tests**
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q -Dtest=ApiDocModeSelectorTest,ApiDocExportFormTest,ApiDocWindowArchitectureTest,ApiDocHistoryMappingTest,ApiDocExportRequestTest,RunApiDocExportUseCaseTest,ApiDocGeneratorProxyTest test`
|
||||||
|
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
- [ ] **Step 6: Perform read-only visual comparison**
|
||||||
|
|
||||||
|
Capture “导出配置” idle and synthetic/controlled running state at the same viewport as the approved reference. Compare reference and implementation side by side for typography, segmented selector, input alignment, progress/action row, and log dimensions. Do not perform a real export or start the local proxy during visual verification.
|
||||||
|
|
||||||
|
- [ ] **Step 7: Run P1 completion gate and commit**
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q test`
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q -DskipTests compile`
|
||||||
|
|
||||||
|
Run: `git diff --check`
|
||||||
|
|
||||||
|
Expected: all exit 0.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git add src/main/java/com/example/workutils/biz/apidoc src/main/java/com/example/workutils/system/ui/ResizableLogPane.java src/main/resources/css/light-theme.css src/test/java/com/example/workutils/biz/apidoc src/test/java/com/example/workutils/system/ui/ResizableLogPaneTest.java
|
||||||
|
git commit -m "按统一规范重排 API 文档导出"
|
||||||
|
```
|
||||||
@@ -0,0 +1,288 @@
|
|||||||
|
# WorkUtils Unified UI P2 Core Plugin Migration 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:** Migrate MyBatis reverse engineering, MyBatis SQL export, scanner, Git sync, and text diff to the P0 page skeleton and visual tokens without changing business behavior.
|
||||||
|
|
||||||
|
**Architecture:** Treat every plugin window as a thin view composer. Shared `PageHeader`, semantic CSS classes, `LoadingButtonBinder`, `OperationStatusPane`, `ExportHistoryPane`, and `ResizableLogPane` replace per-window font/style/layout code; application/use-case classes remain unchanged.
|
||||||
|
|
||||||
|
**Tech Stack:** Java 17, JavaFX 17.0.15, AtlantaFX 2.0.1, P0/P1 shared UI components, JUnit 5, Maven Wrapper.
|
||||||
|
|
||||||
|
## Global Constraints
|
||||||
|
|
||||||
|
- Do not change request DTOs, use cases, generators, scanners, Git operations, diff algorithms, persistence formats, or output files.
|
||||||
|
- Do not add a second page title when the application header already names the active plugin; content uses `PageHeader` only when it adds a useful description.
|
||||||
|
- Remove per-window `Microsoft YaHei`, arbitrary font sizes, background `setStyle(...)`, and duplicated log/status styling from the touched windows.
|
||||||
|
- Use only P0 `AppIcon`/`IconFactory`; no Emoji in visible controls.
|
||||||
|
- Use `LoadingButtonBinder` and `OperationStatusPane` as the only asynchronous visual-state entry points.
|
||||||
|
- Preserve true cancellation semantics: SQL export may cancel; scanner must not show a false cancel control.
|
||||||
|
- Each plugin migration is an independent commit with focused tests and a screenshot comparison.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
### Create
|
||||||
|
|
||||||
|
- `src/test/java/com/example/workutils/system/ui/CorePluginVisualArchitectureTest.java` — source-level guard for inline styles, arbitrary fonts, Emoji, and duplicate loading code.
|
||||||
|
|
||||||
|
### Modify
|
||||||
|
|
||||||
|
- `src/main/java/com/example/workutils/biz/mybatisgenerator/MyBatisGeneratorWindow.java`
|
||||||
|
- `src/test/java/com/example/workutils/biz/mybatisgenerator/MyBatisGeneratorWindowArchitectureTest.java`
|
||||||
|
- `src/main/java/com/example/workutils/biz/mybatisexport/MyBatisExportWindow.java`
|
||||||
|
- `src/test/java/com/example/workutils/biz/mybatisexport/MyBatisExportHistoryMappingTest.java`
|
||||||
|
- `src/main/java/com/example/workutils/biz/scanner/ScannerWindow.java`
|
||||||
|
- `src/test/java/com/example/workutils/biz/scanner/ScannerHistoryMappingTest.java`
|
||||||
|
- `src/main/java/com/example/workutils/biz/git/GitSyncWindow.java`
|
||||||
|
- `src/main/java/com/example/workutils/biz/diff/DiffWindow.java`
|
||||||
|
- `src/main/resources/css/light-theme.css`
|
||||||
|
|
||||||
|
## Task 1: Establish migration guardrails
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `src/test/java/com/example/workutils/system/ui/CorePluginVisualArchitectureTest.java`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Produces a test helper `source(String relativePath): String`. Start the parameterized contract with the MyBatis generator only; add each remaining path in the task that migrates it so every commit finishes green.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write the failing architecture test**
|
||||||
|
|
||||||
|
```java
|
||||||
|
private static String source(String relativePath) throws IOException {
|
||||||
|
return Files.readString(Path.of("src/main/java/com/example/workutils", relativePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ValueSource(strings = {
|
||||||
|
"biz/mybatisgenerator/MyBatisGeneratorWindow.java"
|
||||||
|
})
|
||||||
|
void corePluginWindowsUseSharedVisualInfrastructure(String relativePath) throws IOException {
|
||||||
|
String source = source(relativePath);
|
||||||
|
assertFalse(source.contains("Font.font(\"Microsoft YaHei\""));
|
||||||
|
assertFalse(source.contains("-fx-background-color:"));
|
||||||
|
assertFalse(source.contains("new ProgressIndicator("));
|
||||||
|
assertTrue(source.contains("Theme.") || source.contains("PageHeader"));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run and record expected failures**
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q -Dtest=CorePluginVisualArchitectureTest test`
|
||||||
|
|
||||||
|
Expected: FAIL for the current MyBatis generator hardcoded fonts/backgrounds. Keep the failure output as the first migration checklist.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Add explicit exclusions for business-rendered content**
|
||||||
|
|
||||||
|
The test scans only JavaFX window source. It must not scan generated HTML from `ApiDocGenerator` or diff HTML because those outputs have their own presentation contracts.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Commit the red test with the first migration task only**
|
||||||
|
|
||||||
|
Do not commit a permanently red standalone changeset. Continue directly to Task 2, make the MyBatis generator case green, and commit the test plus that migration together.
|
||||||
|
|
||||||
|
## Task 2: Migrate MyBatis reverse engineering
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/main/java/com/example/workutils/biz/mybatisgenerator/MyBatisGeneratorWindow.java`
|
||||||
|
- Modify: `src/test/java/com/example/workutils/biz/mybatisgenerator/MyBatisGeneratorWindowArchitectureTest.java`
|
||||||
|
- Modify: `src/main/resources/css/light-theme.css`
|
||||||
|
- Include: `src/test/java/com/example/workutils/system/ui/CorePluginVisualArchitectureTest.java`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Preserves: `QueryTablesUseCase`, `GenerateArtifactsUseCase`, output planning, directory safety, and both operation controllers.
|
||||||
|
- Consumes: `PageHeader`, tokenized controls, `LoadingButtonBinder`, and font-size-free `OperationStatusPane`.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add failing window structure assertions**
|
||||||
|
|
||||||
|
```java
|
||||||
|
assertTrue(source.contains("PageHeader"));
|
||||||
|
assertTrue(source.contains("LoadingButtonBinder.bind(queryBtn"));
|
||||||
|
assertTrue(source.contains("LoadingButtonBinder.bind(generateBtn"));
|
||||||
|
assertFalse(source.contains("Theme.heading(fsSection() + 4)"));
|
||||||
|
assertFalse(source.contains("Font.font(\"Consolas\""));
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run focused tests and verify failure**
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q -Dtest=MyBatisGeneratorWindowArchitectureTest,CorePluginVisualArchitectureTest test`
|
||||||
|
|
||||||
|
Expected: FAIL on old title/font/inline styles.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Recompose the window**
|
||||||
|
|
||||||
|
Use a compact data-source context row, two-column split for project tree and table/output form, semantic section headers, and the existing bottom generate action. Replace direct font/style calls with style classes. Keep current divider behavior and all event handlers.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Normalize the two operation rows**
|
||||||
|
|
||||||
|
Query progress remains near table matching; generation progress remains with the final action bar. Do not merge the two controllers. Disable only controls that mutate each operation’s request.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Run all generator tests**
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q -Dtest=MyBatisGeneratorWindowArchitectureTest,GenerationPlannerTest,GeneratorRequestTest,GeneratorUseCaseTest,OutputLayoutPolicyTest,OutputSnapshotTest,DatasourceSummaryTest test`
|
||||||
|
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
- [ ] **Step 6: Capture and compare**
|
||||||
|
|
||||||
|
Capture the generator idle state and a controlled query-running state. Verify 20/15/14/13 typography, aligned directory rows, visible output controls, and no bottom clipping at 1024px width.
|
||||||
|
|
||||||
|
- [ ] **Step 7: Commit**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git add src/main/java/com/example/workutils/biz/mybatisgenerator/MyBatisGeneratorWindow.java src/test/java/com/example/workutils/biz/mybatisgenerator/MyBatisGeneratorWindowArchitectureTest.java src/test/java/com/example/workutils/system/ui/CorePluginVisualArchitectureTest.java src/main/resources/css/light-theme.css
|
||||||
|
git commit -m "统一 MyBatis 逆向工程界面"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Task 3: Migrate MyBatis SQL export and scanner
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/main/java/com/example/workutils/biz/mybatisexport/MyBatisExportWindow.java`
|
||||||
|
- Modify: `src/main/java/com/example/workutils/biz/scanner/ScannerWindow.java`
|
||||||
|
- Modify: `src/test/java/com/example/workutils/biz/mybatisexport/MyBatisExportHistoryMappingTest.java`
|
||||||
|
- Modify: `src/test/java/com/example/workutils/biz/scanner/ScannerHistoryMappingTest.java`
|
||||||
|
- Modify: `src/test/java/com/example/workutils/system/ui/CorePluginVisualArchitectureTest.java`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Preserves: `SqlExportRequest`, `RunSqlExportUseCase`, `ScanRequest`, `RunScanUseCase`, and history mappings.
|
||||||
|
- Consumes: shared page/form classes and `ResizableLogPane`.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add failing cancellation and structure assertions**
|
||||||
|
|
||||||
|
First extend `@ValueSource` with `MyBatisExportWindow.java` and `ScannerWindow.java`, then add:
|
||||||
|
|
||||||
|
```java
|
||||||
|
assertTrue(sqlSource.contains("new OperationStatusPane(exportOperation, true)"));
|
||||||
|
assertTrue(scannerSource.contains("new OperationStatusPane(scanOperation, false)"));
|
||||||
|
assertFalse(scannerSource.contains("停止当前任务"));
|
||||||
|
assertFalse(sqlSource.contains("logArea.setStyle("));
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run focused tests and verify failure**
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q -Dtest=CorePluginVisualArchitectureTest,RunSqlExportCancellationTest,ScanProgressTest test`
|
||||||
|
|
||||||
|
Expected: FAIL on old constructors/styles; cancellation domain tests remain green.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Migrate SQL export**
|
||||||
|
|
||||||
|
Lay out filter controls as one semantic form row, keep export action/progress together, use `ResizableLogPane` for current-run output, and retain `ExportHistoryPane` below. Keep the real SQL-export cancel control.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Migrate scanner**
|
||||||
|
|
||||||
|
Lay out scan type and pattern as a compact configuration section, keep the non-cancellable status pane, and use shared history/log visuals. Do not add a stop button.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Run focused tests**
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q -Dtest=SqlExportRequestTest,SqlExportProgressTest,RunSqlExportCancellationTest,MyBatisExportHistoryMappingTest,ScanRequestTest,ScanProgressTest,ScannerHistoryMappingTest,CorePluginVisualArchitectureTest test`
|
||||||
|
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
- [ ] **Step 6: Capture and compare both pages**
|
||||||
|
|
||||||
|
Use idle/synthetic running states. Confirm SQL export exposes cancel while scanner does not, histories remain usable, and controls do not jump when loading begins.
|
||||||
|
|
||||||
|
- [ ] **Step 7: Commit**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git add src/main/java/com/example/workutils/biz/mybatisexport/MyBatisExportWindow.java src/main/java/com/example/workutils/biz/scanner/ScannerWindow.java src/test/java/com/example/workutils/biz/mybatisexport src/test/java/com/example/workutils/biz/scanner src/test/java/com/example/workutils/system/ui/CorePluginVisualArchitectureTest.java
|
||||||
|
git commit -m "统一导出与扫描插件界面"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Task 4: Migrate Git sync without touching Git behavior
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/main/java/com/example/workutils/biz/git/GitSyncWindow.java`
|
||||||
|
- Modify: `src/test/java/com/example/workutils/system/ui/CorePluginVisualArchitectureTest.java`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Preserves all tabs, `RunGitSyncUseCase`, `GitSyncRequest`, filters, remote URL presentation, damaged repository actions, and confirmation flow.
|
||||||
|
- Consumes shared tabs, table, form, log, loading, and status styles.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add failing Git window assertions**
|
||||||
|
|
||||||
|
First extend `@ValueSource` with `GitSyncWindow.java`, then add:
|
||||||
|
|
||||||
|
```java
|
||||||
|
assertTrue(source.contains("new Tab(\"项目列表\")"));
|
||||||
|
assertTrue(source.contains("new Tab(\"同步执行\")"));
|
||||||
|
assertTrue(source.contains("new Tab(\"损坏仓库处理\")"));
|
||||||
|
assertFalse(source.contains("btn.setStyle("));
|
||||||
|
assertFalse(source.contains("projectTable.setStyle("));
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run tests and verify failure**
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q -Dtest=CorePluginVisualArchitectureTest,RunGitSyncUseCaseTest,ProjectFilterTest,RemoteUrlDisplayTest test`
|
||||||
|
|
||||||
|
Expected: FAIL only on visual architecture assertions.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Migrate each existing tab in place**
|
||||||
|
|
||||||
|
Use global tab styles, tokenized toolbar/form spacing, shared table/log styles, and semantic warning button classes. Do not rename or reorder tabs. Preserve destructive confirmation text and never invoke the destructive action during visual verification.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run focused tests and capture**
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q -Dtest=CorePluginVisualArchitectureTest,RunGitSyncUseCaseTest,ProjectFilterTest,RemoteUrlDisplayTest test`
|
||||||
|
|
||||||
|
Expected: PASS. Capture all three tabs read-only and verify row height, URL truncation, and warning action hierarchy.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git add src/main/java/com/example/workutils/biz/git/GitSyncWindow.java src/test/java/com/example/workutils/system/ui/CorePluginVisualArchitectureTest.java src/main/resources/css/light-theme.css
|
||||||
|
git commit -m "统一 Git 同步插件界面"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Task 5: Migrate text diff while preserving the renderer
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/main/java/com/example/workutils/biz/diff/DiffWindow.java`
|
||||||
|
- Modify: `src/test/java/com/example/workutils/system/ui/CorePluginVisualArchitectureTest.java`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Preserves: `DiffEngine`, input actions, navigation between differences, copy/save actions, and WebView result rendering.
|
||||||
|
- Consumes shared input, split-pane, action-bar, status, and focus styles.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add failing diff visual assertions**
|
||||||
|
|
||||||
|
First extend `@ValueSource` with `DiffWindow.java`, then add:
|
||||||
|
|
||||||
|
```java
|
||||||
|
assertTrue(source.contains("LoadingButtonBinder.bind(compareBtn"));
|
||||||
|
assertFalse(source.contains("\"▶ 开始对比\""));
|
||||||
|
assertFalse(source.contains("webView.setStyle("));
|
||||||
|
assertFalse(source.contains("statusLabel.setStyle("));
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run and verify failure**
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q -Dtest=CorePluginVisualArchitectureTest,DiffEngineLineTest,DiffEngineCharacterTest test`
|
||||||
|
|
||||||
|
Expected: FAIL on Emoji/inline styles; engine tests remain green.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Migrate the JavaFX chrome only**
|
||||||
|
|
||||||
|
Replace the play-character button text with visible “开始对比” plus `AppIcon.DIFF`; apply shared split-pane, input, result header, WebView border, and status classes. Do not alter HTML generation or diff algorithms.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run tests and capture**
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q -Dtest=CorePluginVisualArchitectureTest,DiffEngineLineTest,DiffEngineCharacterTest test`
|
||||||
|
|
||||||
|
Expected: PASS. Capture empty, compared, and difference-navigation states using non-sensitive sample text.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git add src/main/java/com/example/workutils/biz/diff/DiffWindow.java src/test/java/com/example/workutils/system/ui/CorePluginVisualArchitectureTest.java src/main/resources/css/light-theme.css
|
||||||
|
git commit -m "统一文本对比插件界面"
|
||||||
|
```
|
||||||
|
|
||||||
|
## P2 Completion Gate
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q test`
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q -DskipTests compile`
|
||||||
|
|
||||||
|
Run: `git diff --check`
|
||||||
|
|
||||||
|
Expected: all exit 0. Review the five plugin screenshot sets at 1440px and 1024px before P3. No test or visual check may perform Git synchronization, export files, generation, or scanning against user projects.
|
||||||
@@ -0,0 +1,336 @@
|
|||||||
|
# WorkUtils Unified UI P3 Limited Adaptation and Finalization 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:** Apply the unified theme to Git review, mobile UI automation, settings, and plugin management without structural rewrites, then remove remaining application-level visual inconsistencies and complete full regression/visual acceptance.
|
||||||
|
|
||||||
|
**Architecture:** Treat the four limited modules as compatibility consumers of P0 components. Only replace visual construction, style classes, fonts, icons, and shell entry points; retain their existing controllers, panes, services, and state machines. Finish with repository-wide architecture guards that prevent regression to Emoji, duplicate settings navigation, arbitrary fonts, and ad-hoc loading code.
|
||||||
|
|
||||||
|
**Tech Stack:** Java 17, JavaFX 17.0.15, AtlantaFX 2.0.1, P0–P2 shared UI components, JUnit 5, Maven Wrapper.
|
||||||
|
|
||||||
|
## Global Constraints
|
||||||
|
|
||||||
|
- Do not refactor Git review window structure, review workflow, service boundaries, or DiffEngine.
|
||||||
|
- Do not split mobile UI automation or rewrite its run/record state machine.
|
||||||
|
- Do not change settings information architecture, persistence, validation, or plugin-management behavior.
|
||||||
|
- Preserve Windows native title-bar controls and the light-only theme.
|
||||||
|
- Settings remains top-right-only in the application shell; no sidebar duplicate may return.
|
||||||
|
- Use Feather through `AppIcon`/`IconFactory`; no visible Emoji or mixed icon set in the application UI.
|
||||||
|
- Visual checks are read-only: do not submit reviews, run mobile automation, change settings, reorder plugins, synchronize Git, or write exports.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
### Create
|
||||||
|
|
||||||
|
- `src/test/java/com/example/workutils/system/ui/UnifiedUiArchitectureTest.java` — repository-wide regression guard.
|
||||||
|
|
||||||
|
### Modify
|
||||||
|
|
||||||
|
- `src/main/java/com/example/workutils/biz/gitreview/GitReviewWindow.java`
|
||||||
|
- `src/main/java/com/example/workutils/biz/mobileuiauto/MobileUiAutomationWindow.java`
|
||||||
|
- `src/main/java/com/example/workutils/system/config/SettingsWindow.java`
|
||||||
|
- `src/main/java/com/example/workutils/system/plugin/PluginManagementWindow.java`
|
||||||
|
- `src/main/java/com/example/workutils/system/plugin/PluginDescriptor.java`
|
||||||
|
- Plugin descriptor/factory classes under `src/main/java/com/example/workutils/biz/*/*Plugin.java`
|
||||||
|
- `src/main/resources/css/light-theme.css`
|
||||||
|
- `src/test/java/com/example/workutils/system/ui/SharedPaneAccessibilityTest.java`
|
||||||
|
- `src/test/java/com/example/workutils/system/plugin/DefaultPluginConfigurationTest.java`
|
||||||
|
|
||||||
|
## Task 1: Add repository-wide visual architecture guards
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `src/test/java/com/example/workutils/system/ui/UnifiedUiArchitectureTest.java`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Produces `source(String): SourceFile` and an explicit `limitedUiSources()` list. Start with Git review only; append mobile, settings/plugin management, then all plugin descriptors in their migration tasks so every task ends green.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write the failing architecture tests**
|
||||||
|
|
||||||
|
```java
|
||||||
|
private record SourceFile(Path path, String text) {}
|
||||||
|
|
||||||
|
private static SourceFile source(String relativePath) throws IOException {
|
||||||
|
Path path = Path.of("src/main/java/com/example/workutils", relativePath);
|
||||||
|
return new SourceFile(path, Files.readString(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<SourceFile> limitedUiSources() throws IOException {
|
||||||
|
return List.of(source("biz/gitreview/GitReviewWindow.java"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long count(List<SourceFile> files, String needle) {
|
||||||
|
return files.stream()
|
||||||
|
.mapToLong(file -> (file.text().length()
|
||||||
|
- file.text().replace(needle, "").length()) / needle.length())
|
||||||
|
.sum();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void applicationUiDoesNotUseEmojiIconsOrHardcodedMicrosoftYaHei() throws IOException {
|
||||||
|
for (SourceFile file : limitedUiSources()) {
|
||||||
|
assertFalse(file.text().contains("Microsoft YaHei"), file.path().toString());
|
||||||
|
assertFalse(file.text().matches("(?s).*new Label\\(\"[⚙🛠✅❌❓▶].*"),
|
||||||
|
file.path().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void settingsAndLoadingHaveSingleOwners() throws IOException {
|
||||||
|
String app = Files.readString(Path.of(
|
||||||
|
"src/main/java/com/example/workutils/WorkUtilsApplication.java"));
|
||||||
|
assertFalse(app.contains("new MainWindow.NavItem(\n AppIcon.SETTINGS, \"系统设置\""));
|
||||||
|
assertEquals(0, count(limitedUiSources(), "setBtnLoading("));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Limit Emoji scanning to JavaFX application controls. Exclude `ApiDocGenerator` HTML output where symbols may be document content rather than application chrome.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run and record failures**
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q -Dtest=UnifiedUiArchitectureTest test`
|
||||||
|
|
||||||
|
Expected: FAIL on Git review fonts/icons. Continue directly to Task 2 before committing.
|
||||||
|
|
||||||
|
## Task 2: Theme-adapt Git review without structural refactoring
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/main/java/com/example/workutils/biz/gitreview/GitReviewWindow.java`
|
||||||
|
- Modify: `src/main/resources/css/light-theme.css`
|
||||||
|
- Include: `src/test/java/com/example/workutils/system/ui/UnifiedUiArchitectureTest.java`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Preserves every existing method signature, pane hierarchy, review action, service call, and loading behavior.
|
||||||
|
- Consumes only P0 tokens/icons/style classes.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add a structural checksum assertion before edits**
|
||||||
|
|
||||||
|
```java
|
||||||
|
assertTrue(source.contains("class GitReviewWindow"));
|
||||||
|
assertTrue(source.contains("GitReviewService"));
|
||||||
|
assertFalse(source.contains("new AppShell("));
|
||||||
|
```
|
||||||
|
|
||||||
|
The window must remain a plugin content view and must not create its own shell.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Replace visual literals only**
|
||||||
|
|
||||||
|
Replace direct fonts, colors, button graphics, and local spinner construction with semantic style classes, `IconFactory`, and `LoadingButtonBinder`. Do not extract panes, rename business methods, or change action ordering.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Run Git review compilation/architecture checks**
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q -Dtest=UnifiedUiArchitectureTest,WindowNotificationDependencyTest test`
|
||||||
|
|
||||||
|
Expected: Git review visual assertions pass; notification boundary remains intact.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Capture read-only Git review states**
|
||||||
|
|
||||||
|
Capture initial/empty/configured views without starting a review. Verify shell typography and icons while confirming the internal layout is structurally unchanged.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git add src/main/java/com/example/workutils/biz/gitreview/GitReviewWindow.java src/main/resources/css/light-theme.css src/test/java/com/example/workutils/system/ui/UnifiedUiArchitectureTest.java
|
||||||
|
git commit -m "适配 Git 评审统一主题"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Task 3: Theme-adapt mobile UI automation without state-machine changes
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/main/java/com/example/workutils/biz/mobileuiauto/MobileUiAutomationWindow.java`
|
||||||
|
- Modify: `src/test/java/com/example/workutils/system/ui/UnifiedUiArchitectureTest.java`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Preserves `MobileAutomationService`, `AutomationRunHandle`, callback types, run/record controls, and current window composition.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add source boundary assertions**
|
||||||
|
|
||||||
|
First append `biz/mobileuiauto/MobileUiAutomationWindow.java` to `limitedUiSources()`, then add:
|
||||||
|
|
||||||
|
```java
|
||||||
|
assertTrue(source.contains("MobileAutomationService"));
|
||||||
|
assertTrue(source.contains("AutomationRunHandle"));
|
||||||
|
assertFalse(source.contains("class RecordingStateMachine"));
|
||||||
|
assertFalse(source.contains("class RunStateMachine"));
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Replace only theme consumers**
|
||||||
|
|
||||||
|
Use token fonts, shared controls, Feather icons, and CSS classes. Preserve current button event handlers and every service/callback interaction. If a control has ad-hoc spinner code, bind its existing operation state through `LoadingButtonBinder` only when an `AsyncOperationController` already exists; otherwise style the existing state without creating a new state machine in P3.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Run compile and architecture tests**
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q -Dtest=UnifiedUiArchitectureTest test`
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q -DskipTests compile`
|
||||||
|
|
||||||
|
Expected: both pass; no new run/record state types appear.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Capture read-only states**
|
||||||
|
|
||||||
|
Capture the idle screen only. Do not connect devices, start recordings, run scripts, or alter execution history.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git add src/main/java/com/example/workutils/biz/mobileuiauto/MobileUiAutomationWindow.java src/test/java/com/example/workutils/system/ui/UnifiedUiArchitectureTest.java src/main/resources/css/light-theme.css
|
||||||
|
git commit -m "适配手机 UI 自动化统一主题"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Task 4: Theme-adapt settings and plugin management
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/main/java/com/example/workutils/system/config/SettingsWindow.java`
|
||||||
|
- Modify: `src/main/java/com/example/workutils/system/plugin/PluginManagementWindow.java`
|
||||||
|
- Modify: `src/test/java/com/example/workutils/system/ui/SharedPaneAccessibilityTest.java`
|
||||||
|
- Modify: `src/test/java/com/example/workutils/system/plugin/DefaultPluginConfigurationTest.java`
|
||||||
|
- Modify: `src/test/java/com/example/workutils/system/ui/UnifiedUiArchitectureTest.java`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Preserves all settings tabs, save/reload behavior, config files, data-source management, plugin enable/order behavior, and drag/drop semantics.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add failing unique-entry and accessibility assertions**
|
||||||
|
|
||||||
|
First append `system/config/SettingsWindow.java` and `system/plugin/PluginManagementWindow.java` to `limitedUiSources()`, then add:
|
||||||
|
|
||||||
|
```java
|
||||||
|
String app = Files.readString(Path.of(
|
||||||
|
"src/main/java/com/example/workutils/WorkUtilsApplication.java"));
|
||||||
|
String management = Files.readString(Path.of(
|
||||||
|
"src/main/java/com/example/workutils/system/plugin/PluginManagementWindow.java"));
|
||||||
|
assertFalse(app.contains("AppIcon.SETTINGS, \"系统设置\","));
|
||||||
|
assertTrue(management.contains("setAccessibleText(\"拖动调整插件顺序\")"));
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run focused tests and verify failure**
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q -Dtest=DefaultPluginConfigurationTest,SharedPaneAccessibilityTest,UnifiedUiArchitectureTest test`
|
||||||
|
|
||||||
|
Expected: FAIL on remaining Unicode grip/icon or missing accessible labels.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Apply theme classes without changing information architecture**
|
||||||
|
|
||||||
|
Replace fonts/icons/inline styles; keep all current tabs and field grouping. Replace the `≡` drag label with Feather `MENU`, preserving drag handlers and user data. Do not move settings sections or change persistence.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run focused tests and capture**
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q -Dtest=DefaultPluginConfigurationTest,SharedPaneAccessibilityTest,UnifiedUiArchitectureTest test`
|
||||||
|
|
||||||
|
Expected: PASS. Open settings from the top-right entry and inspect tabs without saving or reordering anything.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git add src/main/java/com/example/workutils/system/config/SettingsWindow.java src/main/java/com/example/workutils/system/plugin/PluginManagementWindow.java src/test/java/com/example/workutils/system/ui/SharedPaneAccessibilityTest.java src/test/java/com/example/workutils/system/plugin/DefaultPluginConfigurationTest.java src/test/java/com/example/workutils/system/ui/UnifiedUiArchitectureTest.java src/main/resources/css/light-theme.css
|
||||||
|
git commit -m "适配设置与插件管理统一主题"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Task 5: Replace remaining descriptor Emoji with semantic icons
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/main/java/com/example/workutils/system/plugin/PluginDescriptor.java`
|
||||||
|
- Modify: plugin classes matching `src/main/java/com/example/workutils/biz/*/*Plugin.java`
|
||||||
|
- Modify: `src/main/java/com/example/workutils/WorkUtilsApplication.java`
|
||||||
|
- Modify: `src/test/java/com/example/workutils/system/plugin/DefaultPluginConfigurationTest.java`
|
||||||
|
- Modify: `src/test/java/com/example/workutils/system/ui/UnifiedUiArchitectureTest.java`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Changes `PluginDescriptor.icon` from `String` to `AppIcon`.
|
||||||
|
- Changes `getIcon(): String` to `getIcon(): AppIcon` and all factory/embedding call sites accordingly.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add a failing descriptor type assertion**
|
||||||
|
|
||||||
|
Expand `limitedUiSources()` to all JavaFX application UI and plugin descriptor/factory sources using `Files.walk`, while explicitly excluding `biz/apidoc/generator/ApiDocGenerator.java` because it renders document content rather than application chrome. Then add:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Test
|
||||||
|
void descriptorCarriesSemanticIconType() {
|
||||||
|
PluginDescriptor descriptor = PluginDescriptor.builder()
|
||||||
|
.id("scan")
|
||||||
|
.name("扫描功能")
|
||||||
|
.icon(AppIcon.SCANNER)
|
||||||
|
.build();
|
||||||
|
AppIcon icon = descriptor.getIcon();
|
||||||
|
assertEquals(AppIcon.SCANNER, icon);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Add a compile-time expectation in call sites by assigning `AppIcon icon = descriptor.getIcon()`.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run and verify failure**
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q -Dtest=DefaultPluginConfigurationTest,UnifiedUiArchitectureTest test`
|
||||||
|
|
||||||
|
Expected: FAIL/compile failure while descriptors still return strings.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Migrate the descriptor type atomically**
|
||||||
|
|
||||||
|
Update constructor, getter, all plugin declarations, `MainWindow.NavItem`, plugin management rows, and embed calls in one changeset. Use the semantic mappings defined in P0; do not expose Feather enum types outside `system.ui.icon`.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run full tests**
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q test`
|
||||||
|
|
||||||
|
Expected: PASS with zero failures/errors.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git add src/main/java/com/example/workutils/system/plugin src/main/java/com/example/workutils/biz src/main/java/com/example/workutils/WorkUtilsApplication.java src/test/java/com/example/workutils/system/plugin src/test/java/com/example/workutils/system/ui/UnifiedUiArchitectureTest.java
|
||||||
|
git commit -m "完成插件语义图标迁移"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Task 6: Full visual, accessibility, and regression acceptance
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify only when acceptance finds a concrete defect in a previously touched P0–P3 file.
|
||||||
|
- Update tests first for every discovered defect.
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Produces no new product API; this task validates the approved design and guards.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Run all automated gates fresh**
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q test`
|
||||||
|
|
||||||
|
Run: `.\mvnw.cmd -q -DskipTests compile`
|
||||||
|
|
||||||
|
Run: `git diff --check`
|
||||||
|
|
||||||
|
Expected: all exit 0, zero test failures/errors.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run repository scans**
|
||||||
|
|
||||||
|
Run: `rg -n 'Microsoft YaHei|setBtnLoading\(|new ProgressIndicator\(|⚙️|🛠|▶' src/main/java/com/example/workutils`
|
||||||
|
|
||||||
|
Expected: no application-chrome matches. Document-content matches are allowed only where `UnifiedUiArchitectureTest` explicitly excludes them.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Capture the full read-only screenshot matrix**
|
||||||
|
|
||||||
|
Capture welcome plus every enabled plugin at 1440px expanded, 1440px collapsed, and 1024px expanded. For API export also capture a controlled loading state. Use non-sensitive fixture/sample data and do not trigger external/file-changing operations.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Compare against the approved visual baseline**
|
||||||
|
|
||||||
|
Place the approved reference and each implementation screenshot side by side at matching viewport/state. Check header 48px, rail 214/56px, unique settings location, type scale, icon baseline, 8px spacing rhythm, control height, row height, border/radius, focus visibility, loading stability, and clipping.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Keyboard/accessibility smoke test**
|
||||||
|
|
||||||
|
Tab through header settings, navigation, primary form controls, action buttons, cancel controls where real, tabs, history actions, and collapsed-rail tooltips. Confirm accessible text changes with operation state and focus never disappears.
|
||||||
|
|
||||||
|
- [ ] **Step 6: Fix defects through red-green cycles**
|
||||||
|
|
||||||
|
For each defect: add one failing focused test, run it to confirm failure, make the smallest P0–P3 file change, rerun the focused test, then rerun the full gate. Do not bundle unrelated visual polish.
|
||||||
|
|
||||||
|
- [ ] **Step 7: Commit final verified fixes**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git add src/main src/test
|
||||||
|
git commit -m "完成统一 UI 视觉验收"
|
||||||
|
```
|
||||||
|
|
||||||
|
If acceptance requires no code changes, do not create an empty commit.
|
||||||
|
|
||||||
|
## P3 Completion Gate
|
||||||
|
|
||||||
|
Completion requires fresh evidence from full tests, compile, `git diff --check`, repository scans, the complete screenshot matrix, side-by-side reference comparisons, and keyboard/accessibility smoke checks. Do not claim the unified UI complete from automated tests alone.
|
||||||
Reference in New Issue
Block a user