diff --git a/docs/development/platform-parity-matrix.csv b/docs/development/platform-parity-matrix.csv
index 6d670ca60..b421f23da 100644
--- a/docs/development/platform-parity-matrix.csv
+++ b/docs/development/platform-parity-matrix.csv
@@ -36,7 +36,7 @@ ai-chat-session,AI,AI 对话,对话会话与流式响应,未实现,不适用,已
ai-chat-history,AI,AI 对话,对话历史、置顶与归档,未实现,不适用,已实现,待验证,AI,Windows 验证历史加载、重命名、置顶、归档、删除和重启后持久化。,,macos/Sources/Lithe/Platform/MacOS/AI,windows/tauri/src/features/ai/services/ai-chat-history-service.ts; windows/tauri/src/features/layout/components/sidebar/sidebar-history.tsx
ai-chat-providers,AI,AI 对话,多 Provider 与工作区范围,未实现,不适用,已实现,待验证,AI,Windows 验证 Provider 切换、凭据边界、工作区范围和模型错误提示。,,macos/Sources/Lithe/Platform/MacOS/AI,windows/tauri/src/features/ai/services/providers; windows/tauri/src/features/ai/lib/ai-workspace-scope.ts
java-runtime-discovery,Java,Java/Maven,JDK 与 Maven 工具链发现,已实现,待验证,已实现,待验证,Java / Maven,配置多个 JDK/Maven 候选,验证发现、选择、版本不匹配和错误提示。,,macos/Sources/Lithe/Services/Java; macos/Sources/Lithe/Views/Run,windows/tauri/src/features/maven; windows/tauri/src/features/workspace
-java-maven-project-settings,Java,Java/Maven,按项目配置 Maven 工具链、settings.xml 与本地仓库,已实现,待验证,已实现,待验证,Java / Maven,在 macOS 项目设置和 Windows Maven 设置页分别切换两个项目,配置 Maven、JDK、settings.xml 与本地仓库,验证保存重开、自动检测展示及项目间隔离;Windows 还需验证写盘失败后可直接重试。 Windows 自定义 settings.xml 后,仓库留空应显示该文件中的目录;验证 XML 实体和 CDATA、继承的项目 JDK/Maven、切换项目丢弃未保存草稿;冷启动 Wrapper 不应触发下载,版本探测卡住时设置窗口保持响应并在超时后清理探测进程。,,macos/Sources/Lithe/Views/App/ProjectRuntimeSettingsView.swift; macos/Sources/Lithe/Services/Java/ProjectRuntimeService.swift,windows/tauri/src/features/settings/components/tabs/maven-settings-panel.tsx; windows/tauri/src/features/maven/stores/maven.store.ts; windows/tauri/src-tauri/src/maven.rs; windows/tauri/src-tauri/src/maven/settings_xml.rs; windows/tauri/src/features/maven/components/maven-detected-value.test.tsx; windows/tauri/src/features/settings/components/tabs/maven-settings-panel.test.tsx
+java-maven-project-settings,Java,Java/Maven,按项目配置 Maven 工具链、settings.xml 与本地仓库,已实现,待验证,已实现,待验证,Java / Maven,在 macOS 项目设置和 Windows Maven 设置页分别切换两个项目,配置 Maven、JDK、settings.xml 与本地仓库,验证保存重开、自动检测展示及项目间隔离;Windows 还需验证写盘失败后可直接重试。 Windows 自定义 settings.xml 后,仓库留空应显示该文件中的目录;验证 XML 实体和 CDATA、继承的项目 JDK/Maven、切换项目丢弃未保存草稿;冷启动 Wrapper 不应触发下载,版本探测卡住时设置窗口保持响应并在超时后清理探测进程;Run 配置显式选择的 Maven/JDK 优先于项目设置,未设置的字段分别继承 Maven 设置;确认主任务与前置任务使用相同工具链,Maven 设置上下文缺失时仍保留显式覆盖,均未配置时交由平台自动发现。,,macos/Sources/Lithe/Views/App/ProjectRuntimeSettingsView.swift; macos/Sources/Lithe/Services/Java/ProjectRuntimeService.swift,windows/tauri/src/features/settings/components/tabs/maven-settings-panel.tsx; windows/tauri/src/features/maven/stores/maven.store.ts; windows/tauri/src-tauri/src/maven.rs; windows/tauri/src-tauri/src/maven/settings_xml.rs; windows/tauri/src/features/maven/components/maven-detected-value.test.tsx; windows/tauri/src/features/settings/components/tabs/maven-settings-panel.test.tsx; windows/tauri/src/features/run/stores/run.store.ts; windows/tauri/src/features/run/stores/run-maven-context.test.ts
java-maven-project,Java,Java/Maven,项目根、模块与源码集识别,已实现,待验证,已实现,待验证,Java / Maven,使用单模块、多模块和非标准源码目录 fixture 对比项目模型。,,macos/Sources/Lithe/Services/Java; shared/contracts/application-boundary.md,windows/tauri/src/features/maven; windows/tauri/src/features/workspace
java-maven-profiles-dependencies,Java,Java/Maven,Profile 与依赖树,已实现,待验证,已实现,待验证,Java / Maven,"切换 Maven profile 并刷新依赖树,确认模块归属、顺序、版本冲突/重复/依赖管理标注和错误边界;在依赖节点超过 6,000 个的项目中确认依赖树完整加载。",,macos/Sources/Lithe/Views/Run; macos/Sources/Lithe/Services/Java; macos/Sources/LitheExecutionModule/Services/MavenService.swift; macos/Sources/Lithe/Platform/MacOS/Persistence/MacMavenDependencyOutputStore.swift; rust/lithe-core/src/project/maven_dependency_tree.rs,windows/tauri/src/features/maven; windows/tauri/src-tauri/src/maven.rs; rust/lithe-core/src/project/maven_dependency_tree.rs
java-maven-toolbar-tooltips,Java,Java/Maven,Maven 工具栏图标悬停说明,已实现,待验证,已实现,待验证,Java / Maven,在 macOS 和 Windows 的 Maven 工具窗口逐一悬停运行、执行目标、重新加载、跳过测试、折叠和设置图标,确认提示与动作一致、提示框宽度适配文字;再检查禁用按钮和窄窗口中的提示换行与边缘约束。,,macos/Sources/Lithe/Views/Run/MavenView.swift; macos/Sources/Lithe/Views/Workbench/WorkbenchHoverTooltip.swift; macos/Tests/LitheTests/WorkbenchHoverTooltipTests.swift,windows/tauri/src/features/maven/components/maven-pane.tsx
diff --git a/docs/development/platform-parity-matrix.md b/docs/development/platform-parity-matrix.md
index 79a8358d5..22c2864a5 100644
--- a/docs/development/platform-parity-matrix.md
+++ b/docs/development/platform-parity-matrix.md
@@ -133,7 +133,7 @@
| 功能组 | 能力点 | macOS | Windows | 负责人 | 验证方式 | 备注 |
| --- | --- | --- | --- | --- | --- | --- |
| Java/Maven | **JDK 与 Maven 工具链发现**
java-runtime-discovery | ✅ 已实现
🔍 待验证
`macos/Sources/Lithe/Services/Java`、`macos/Sources/Lithe/Views/Run` | ✅ 已实现
🔍 待验证
`windows/tauri/src/features/maven`、`windows/tauri/src/features/workspace` | Java / Maven | 配置多个 JDK/Maven 候选,验证发现、选择、版本不匹配和错误提示。 | |
-| Java/Maven | **按项目配置 Maven 工具链、settings.xml 与本地仓库**
java-maven-project-settings | ✅ 已实现
🔍 待验证
`macos/Sources/Lithe/Views/App/ProjectRuntimeSettingsView.swift`、`macos/Sources/Lithe/Services/Java/ProjectRuntimeService.swift` | ✅ 已实现
🔍 待验证
`windows/tauri/src/features/settings/components/tabs/maven-settings-panel.tsx`、`windows/tauri/src/features/maven/stores/maven.store.ts`、`windows/tauri/src-tauri/src/maven.rs`、`windows/tauri/src-tauri/src/maven/settings_xml.rs`、`windows/tauri/src/features/maven/components/maven-detected-value.test.tsx`、`windows/tauri/src/features/settings/components/tabs/maven-settings-panel.test.tsx` | Java / Maven | 在 macOS 项目设置和 Windows Maven 设置页分别切换两个项目,配置 Maven、JDK、settings.xml 与本地仓库,验证保存重开、自动检测展示及项目间隔离;Windows 还需验证写盘失败后可直接重试。 Windows 自定义 settings.xml 后,仓库留空应显示该文件中的目录;验证 XML 实体和 CDATA、继承的项目 JDK/Maven、切换项目丢弃未保存草稿;冷启动 Wrapper 不应触发下载,版本探测卡住时设置窗口保持响应并在超时后清理探测进程。 | |
+| Java/Maven | **按项目配置 Maven 工具链、settings.xml 与本地仓库**
java-maven-project-settings | ✅ 已实现
🔍 待验证
`macos/Sources/Lithe/Views/App/ProjectRuntimeSettingsView.swift`、`macos/Sources/Lithe/Services/Java/ProjectRuntimeService.swift` | ✅ 已实现
🔍 待验证
`windows/tauri/src/features/settings/components/tabs/maven-settings-panel.tsx`、`windows/tauri/src/features/maven/stores/maven.store.ts`、`windows/tauri/src-tauri/src/maven.rs`、`windows/tauri/src-tauri/src/maven/settings_xml.rs`、`windows/tauri/src/features/maven/components/maven-detected-value.test.tsx`、`windows/tauri/src/features/settings/components/tabs/maven-settings-panel.test.tsx`、`windows/tauri/src/features/run/stores/run.store.ts`、`windows/tauri/src/features/run/stores/run-maven-context.test.ts` | Java / Maven | 在 macOS 项目设置和 Windows Maven 设置页分别切换两个项目,配置 Maven、JDK、settings.xml 与本地仓库,验证保存重开、自动检测展示及项目间隔离;Windows 还需验证写盘失败后可直接重试。 Windows 自定义 settings.xml 后,仓库留空应显示该文件中的目录;验证 XML 实体和 CDATA、继承的项目 JDK/Maven、切换项目丢弃未保存草稿;冷启动 Wrapper 不应触发下载,版本探测卡住时设置窗口保持响应并在超时后清理探测进程;Run 配置显式选择的 Maven/JDK 优先于项目设置,未设置的字段分别继承 Maven 设置;确认主任务与前置任务使用相同工具链,Maven 设置上下文缺失时仍保留显式覆盖,均未配置时交由平台自动发现。 | |
| Java/Maven | **项目根、模块与源码集识别**
java-maven-project | ✅ 已实现
🔍 待验证
`macos/Sources/Lithe/Services/Java`、`shared/contracts/application-boundary.md` | ✅ 已实现
🔍 待验证
`windows/tauri/src/features/maven`、`windows/tauri/src/features/workspace` | Java / Maven | 使用单模块、多模块和非标准源码目录 fixture 对比项目模型。 | |
| Java/Maven | **Profile 与依赖树**
java-maven-profiles-dependencies | ✅ 已实现
🔍 待验证
`macos/Sources/Lithe/Views/Run`、`macos/Sources/Lithe/Services/Java`、`macos/Sources/LitheExecutionModule/Services/MavenService.swift`、`macos/Sources/Lithe/Platform/MacOS/Persistence/MacMavenDependencyOutputStore.swift`、`rust/lithe-core/src/project/maven_dependency_tree.rs` | ✅ 已实现
🔍 待验证
`windows/tauri/src/features/maven`、`windows/tauri/src-tauri/src/maven.rs`、`rust/lithe-core/src/project/maven_dependency_tree.rs` | Java / Maven | 切换 Maven profile 并刷新依赖树,确认模块归属、顺序、版本冲突/重复/依赖管理标注和错误边界;在依赖节点超过 6,000 个的项目中确认依赖树完整加载。 | |
| Java/Maven | **Maven 工具栏图标悬停说明**
java-maven-toolbar-tooltips | ✅ 已实现
🔍 待验证
`macos/Sources/Lithe/Views/Run/MavenView.swift`、`macos/Sources/Lithe/Views/Workbench/WorkbenchHoverTooltip.swift`、`macos/Tests/LitheTests/WorkbenchHoverTooltipTests.swift` | ✅ 已实现
🔍 待验证
`windows/tauri/src/features/maven/components/maven-pane.tsx` | Java / Maven | 在 macOS 和 Windows 的 Maven 工具窗口逐一悬停运行、执行目标、重新加载、跳过测试、折叠和设置图标,确认提示与动作一致、提示框宽度适配文字;再检查禁用按钮和窄窗口中的提示换行与边缘约束。 | |
diff --git a/shared/platform-feature-matrix.json b/shared/platform-feature-matrix.json
index 2b37a65f9..9218cf97c 100644
--- a/shared/platform-feature-matrix.json
+++ b/shared/platform-feature-matrix.json
@@ -986,13 +986,15 @@
"windows/tauri/src-tauri/src/maven.rs",
"windows/tauri/src-tauri/src/maven/settings_xml.rs",
"windows/tauri/src/features/maven/components/maven-detected-value.test.tsx",
- "windows/tauri/src/features/settings/components/tabs/maven-settings-panel.test.tsx"
+ "windows/tauri/src/features/settings/components/tabs/maven-settings-panel.test.tsx",
+ "windows/tauri/src/features/run/stores/run.store.ts",
+ "windows/tauri/src/features/run/stores/run-maven-context.test.ts"
],
"implementationStatus": "implemented",
"verificationStatus": "pending"
},
"owner": "Java / Maven",
- "verification": "在 macOS 项目设置和 Windows Maven 设置页分别切换两个项目,配置 Maven、JDK、settings.xml 与本地仓库,验证保存重开、自动检测展示及项目间隔离;Windows 还需验证写盘失败后可直接重试。 Windows 自定义 settings.xml 后,仓库留空应显示该文件中的目录;验证 XML 实体和 CDATA、继承的项目 JDK/Maven、切换项目丢弃未保存草稿;冷启动 Wrapper 不应触发下载,版本探测卡住时设置窗口保持响应并在超时后清理探测进程。"
+ "verification": "在 macOS 项目设置和 Windows Maven 设置页分别切换两个项目,配置 Maven、JDK、settings.xml 与本地仓库,验证保存重开、自动检测展示及项目间隔离;Windows 还需验证写盘失败后可直接重试。 Windows 自定义 settings.xml 后,仓库留空应显示该文件中的目录;验证 XML 实体和 CDATA、继承的项目 JDK/Maven、切换项目丢弃未保存草稿;冷启动 Wrapper 不应触发下载,版本探测卡住时设置窗口保持响应并在超时后清理探测进程;Run 配置显式选择的 Maven/JDK 优先于项目设置,未设置的字段分别继承 Maven 设置;确认主任务与前置任务使用相同工具链,Maven 设置上下文缺失时仍保留显式覆盖,均未配置时交由平台自动发现。"
},
{
"id": "java-maven-project",
diff --git a/windows/tauri/src/features/run/stores/run-maven-context.test.ts b/windows/tauri/src/features/run/stores/run-maven-context.test.ts
index eab684105..af0b7590a 100644
--- a/windows/tauri/src/features/run/stores/run-maven-context.test.ts
+++ b/windows/tauri/src/features/run/stores/run-maven-context.test.ts
@@ -172,98 +172,103 @@ describe("Maven-backed Run context", () => {
]);
});
- test("a configured Maven path overrides the project Maven context", async () => {
- const automaticContext: MavenLaunchContext = {
- ...mavenContext,
- mavenExecutablePath: "D:/project/mvn.cmd",
- javaHomePath: "C:/project/jdk",
- };
- const resolveRunLaunch = mock(async () => ({
- executable: "D:/Tools/mvn.cmd",
- workingDirectory: "D:/work/reactor",
- environment: {},
- }));
- const dependencies: RunStoreDependencies = {
- createLaunchPlan: mock(async () => ({
- executable: { toolchain: "project-maven" as const },
- arguments: ["-B", "spring-boot:run"],
- workingDirectory: "reactor",
- })),
- mavenLaunchContextForWorkspace: mock(async () => automaticContext),
- resolveRunLaunch,
- saveWorkspaceBeforeLaunch: mock(async () => undefined),
- executePreLaunchStep: mock(async () => ({ exitCode: 0, output: "" })),
- startRunProcess: mock(async () => undefined),
- stopRunProcess: mock(async () => undefined),
- seedMavenLocalConfiguration: () => undefined,
- prepareJavaRunLaunch: mock(async () => null),
- };
- const store = createRunStore("workspace", dependencies);
- store.setState({
- root: "D:/work",
- configurations: [
- {
- ...configuration,
- mavenExecutablePath: "D:/legacy/mvn.cmd",
- mavenJavaHomePath: "C:/legacy/jdk",
- },
- ],
- diagnostics: [],
- effectiveRuntimeExecutablePaths: {},
- });
+ const pathCases = [
+ {
+ name: "explicit configuration overrides project settings",
+ context: mavenContext,
+ selected: { mavenExecutablePath: "D:/selected/mvn.cmd", mavenJavaHomePath: "C:/selected/jdk" },
+ expected: { mavenExecutablePath: "D:/selected/mvn.cmd", mavenJavaHomePath: "C:/selected/jdk" },
+ },
+ {
+ name: "explicit configuration works without a Maven context",
+ context: null,
+ selected: { mavenExecutablePath: "D:/selected/mvn.cmd", mavenJavaHomePath: "C:/selected/jdk" },
+ expected: { mavenExecutablePath: "D:/selected/mvn.cmd", mavenJavaHomePath: "C:/selected/jdk" },
+ },
+ {
+ name: "blank configuration inherits project settings",
+ context: mavenContext,
+ selected: { mavenExecutablePath: " ", mavenJavaHomePath: "" },
+ expected: { mavenExecutablePath: mavenContext.mavenExecutablePath, mavenJavaHomePath: mavenContext.javaHomePath },
+ },
+ {
+ name: "Maven override still inherits the project Maven JDK",
+ context: mavenContext,
+ selected: { mavenExecutablePath: "D:/selected/mvn.cmd", mavenJavaHomePath: "" },
+ expected: { mavenExecutablePath: "D:/selected/mvn.cmd", mavenJavaHomePath: mavenContext.javaHomePath },
+ },
+ {
+ name: "Maven JDK override still inherits the project Maven",
+ context: mavenContext,
+ selected: { mavenExecutablePath: "", mavenJavaHomePath: "C:/selected/jdk" },
+ expected: { mavenExecutablePath: mavenContext.mavenExecutablePath, mavenJavaHomePath: "C:/selected/jdk" },
+ },
+ {
+ name: "blank configuration without context delegates automatic discovery to the host",
+ context: null,
+ selected: { mavenExecutablePath: "", mavenJavaHomePath: " " },
+ expected: { mavenExecutablePath: "", mavenJavaHomePath: "" },
+ },
+ ];
- await store.getState().actions.runConfiguration(configuration.id);
+ for (const scenario of pathCases) {
+ test(`${scenario.name} for launch and pre-launch`, async () => {
+ const resolveRunLaunch = mock(async (
+ request: Parameters[0],
+ ) => ({
+ executable: request.mavenExecutablePath || "mvn",
+ workingDirectory: "D:/work/reactor",
+ environment: { JAVA_HOME: request.mavenJavaHomePath || "C:/automatic/jdk" },
+ }));
+ const executePreLaunchStep = mock(async () => ({ exitCode: 0, output: "" }));
+ const startRunProcess = mock(async () => undefined);
+ const dependencies: RunStoreDependencies = {
+ createLaunchPlan: mock(async () => ({
+ executable: { toolchain: "project-maven" as const },
+ arguments: ["-B", "spring-boot:run"],
+ workingDirectory: "reactor",
+ preLaunchSteps: [{
+ executable: { toolchain: "project-maven" as const },
+ arguments: ["-B", "compile"],
+ }],
+ })),
+ mavenLaunchContextForWorkspace: mock(async () => scenario.context),
+ resolveRunLaunch,
+ saveWorkspaceBeforeLaunch: mock(async () => undefined),
+ executePreLaunchStep,
+ startRunProcess,
+ stopRunProcess: mock(async () => undefined),
+ seedMavenLocalConfiguration: () => undefined,
+ prepareJavaRunLaunch: mock(async () => null),
+ };
+ const store = createRunStore("workspace", dependencies);
+ store.setState({
+ root: "D:/work",
+ configurations: [{ ...configuration, ...scenario.selected }],
+ diagnostics: [],
+ effectiveRuntimeExecutablePaths: {},
+ });
- expect(resolveRunLaunch).toHaveBeenCalledWith(
- expect.objectContaining({
- mavenExecutablePath: "D:/legacy/mvn.cmd",
- mavenJavaHomePath: "C:/legacy/jdk",
- }),
- );
- });
+ expect(await store.getState().actions.runConfiguration(configuration.id)).toBe(configuration.id);
- test("an empty run configuration Maven path uses the project Maven context", async () => {
- const resolveRunLaunch = mock(async () => ({
- executable: "D:/Tools/mvn.cmd",
- workingDirectory: "D:/work/reactor",
- environment: {},
- }));
- const dependencies: RunStoreDependencies = {
- createLaunchPlan: mock(async () => ({
- executable: { toolchain: "project-maven" as const },
+ expect(resolveRunLaunch).toHaveBeenCalledTimes(2);
+ for (const [request] of resolveRunLaunch.mock.calls) {
+ expect(request).toEqual(expect.objectContaining(scenario.expected));
+ }
+ const resolvedPaths = {
+ executable: scenario.expected.mavenExecutablePath || "mvn",
+ environment: { JAVA_HOME: scenario.expected.mavenJavaHomePath || "C:/automatic/jdk" },
+ };
+ expect(executePreLaunchStep).toHaveBeenCalledTimes(1);
+ expect(executePreLaunchStep).toHaveBeenCalledWith(expect.objectContaining({
+ ...resolvedPaths,
+ arguments: ["-B", "compile"],
+ }));
+ expect(startRunProcess).toHaveBeenCalledTimes(1);
+ expect(startRunProcess).toHaveBeenCalledWith(expect.objectContaining({
+ ...resolvedPaths,
arguments: ["-B", "spring-boot:run"],
- workingDirectory: "reactor",
- })),
- mavenLaunchContextForWorkspace: mock(async () => mavenContext),
- resolveRunLaunch,
- saveWorkspaceBeforeLaunch: mock(async () => undefined),
- executePreLaunchStep: mock(async () => ({ exitCode: 0, output: "" })),
- startRunProcess: mock(async () => undefined),
- stopRunProcess: mock(async () => undefined),
- seedMavenLocalConfiguration: () => undefined,
- prepareJavaRunLaunch: mock(async () => null),
- };
- const store = createRunStore("workspace", dependencies);
- store.setState({
- root: "D:/work",
- configurations: [
- {
- ...configuration,
- mavenExecutablePath: " ",
- mavenJavaHomePath: "",
- },
- ],
- diagnostics: [],
- effectiveRuntimeExecutablePaths: {},
+ }));
});
-
- await store.getState().actions.runConfiguration(configuration.id);
-
- expect(resolveRunLaunch).toHaveBeenCalledWith(
- expect.objectContaining({
- mavenExecutablePath: "D:/Tools/apache-maven",
- mavenJavaHomePath: "C:/Java/jdk-21",
- }),
- );
- });
+ }
});