From 93d6d93815a3d74750ec4981bca2d0494b1fcecb Mon Sep 17 00:00:00 2001 From: lincube Date: Wed, 29 Apr 2026 10:16:25 +0800 Subject: [PATCH] Migrate to Avalonia 12 and Plugin SDK v5 Upgrade project to the Avalonia 12 baseline and Plugin SDK v5: centralize Avalonia packages, remove legacy WebView.Avalonia usage (use NativeWebView/WebView2 EnvironmentRequested), and update Fluent/Material icon/package usages. Bump multiple package/project versions to 5.0.0 and Avalonia 12.0.1, update plugin template and README/docs to SDK v5, and add PLUGIN_SDK_V5_MIGRATION.md. Also fix runtime/behavior bugs: make DataLocationResolver use a fixed bootstrap launcher data path and avoid recursive ResolveDataRoot; add legacy-state handling and extraction in OobeStateService; and update component settings tests to reflect migrated storage (DB/backup) and reset cache for test reloads. Various csproj, tests, and docs updated to reflect the migration and ensure build/test compatibility. --- .../specs/avalonia-12-migration/checklist.md | 31 +++++--- .trae/specs/avalonia-12-migration/spec.md | 78 ++++++++----------- .trae/specs/avalonia-12-migration/tasks.md | 53 ++++--------- AGENTS.md | 4 +- .../Services/DataLocationResolver.cs | 21 +++-- .../Services/OobeStateService.cs | 33 ++++++-- ...inDesktop.PluginIsolation.Contracts.csproj | 2 +- ...MountainDesktop.PluginIsolation.Ipc.csproj | 2 +- .../LanMountainDesktop.PluginSdk.csproj | 2 +- LanMountainDesktop.PluginSdk/README.md | 2 +- .../content/.template.config/template.json | 2 +- .../content/plugin.json | 2 +- ...LanMountainDesktop.Shared.Contracts.csproj | 2 +- .../LanMountainDesktop.Shared.IPC.csproj | 2 +- .../ComponentSettingsServiceTests.cs | 39 ++++++---- .../OobeStateServiceTests.cs | 2 +- README.md | 4 +- docs/PLUGIN_DEVELOPMENT.md | 14 ++-- docs/PLUGIN_SDK_V5_MIGRATION.md | 34 ++++++++ docs/PRODUCT.md | 4 +- .../01-快速开始/02-三分钟创建第一个插件.md | 2 +- .../01-快速开始/03-插件项目结构详解.md | 10 +-- .../02-核心概念与原理/01-插件生命周期.md | 2 +- .../07-发布与运营/01-插件打包规范.md | 4 +- docs/ai/DOC_SOURCES.md | 2 +- 25 files changed, 197 insertions(+), 156 deletions(-) create mode 100644 docs/PLUGIN_SDK_V5_MIGRATION.md diff --git a/.trae/specs/avalonia-12-migration/checklist.md b/.trae/specs/avalonia-12-migration/checklist.md index 2800726..90ebdfa 100644 --- a/.trae/specs/avalonia-12-migration/checklist.md +++ b/.trae/specs/avalonia-12-migration/checklist.md @@ -1,14 +1,21 @@ # Checklist -- [ ] `SettingsWindow.ApplyChromeMode()` 不再使用 `ExtendClientAreaChromeHints` 和 `SystemDecorations` -- [ ] `ComponentEditorWindow.ApplyChromeMode()` 不再使用 `ExtendClientAreaChromeHints` 和 `SystemDecorations` -- [ ] 所有 `.axaml` 文件中的 `SystemDecorations` 已替换为 `WindowDecorations` -- [ ] `MainWindow.ComponentSystem.cs` 中 `centerLeft` 和 `positions` 变量已正确定义 -- [ ] `MainWindow.DesktopPaging.cs` 中 `child` 和 `_isThreeFingerOrRightDragSwipeActive` 变量已正确定义 -- [ ] `App.axaml.cs` 中 `BindingPlugins.DataValidators` 代码已移除 -- [ ] `DesktopComponentFailureView.cs` 使用 `ClipboardExtensions.SetTextAsync` -- [ ] `MonetColorService.cs` 使用正确的 `Bitmap.CopyPixels` 签名 -- [ ] `SettingsWindow.axaml.cs` 使用 `FluentIcons.Avalonia.FluentIcon` 替代 `SymbolIconSource` -- [ ] 所有 `TextBox.Watermark` 已替换为 `PlaceholderText` -- [ ] `dotnet build LanMountainDesktop.slnx -c Debug` 0 errors, 0 warnings(过时 API 警告) -- [ ] `dotnet test LanMountainDesktop.slnx -c Debug` 全部通过 +- [x] `Directory.Packages.props` contains the Avalonia 12 dependency baseline. +- [x] Main host references `Avalonia.Controls.WebView`. +- [x] Source no longer references `WebView.Avalonia`, `AvaloniaWebView`, or `.UseDesktopWebView()`. +- [x] `BrowserWidget` uses `NativeWebView.Source`, `Navigate`, `Refresh()`, `NavigationStarted`, and `EnvironmentRequested`. +- [x] WebView blanking navigates to `about:blank`. +- [x] Plugin SDK package version is `5.0.0`. +- [x] `PluginSdkInfo.ApiVersion` is `5.0.0`. +- [x] Plugin template package version default is `5.0.0`. +- [x] Plugin template manifest `apiVersion` is `5.0.0`. +- [x] Launcher data location config resolution cannot recurse through `ResolveDataRoot()`. +- [x] `OobeStateServiceTests` pass. +- [x] `dotnet build LanMountainDesktop.slnx -c Debug` has 0 errors. +- [x] `dotnet test LanMountainDesktop.slnx -c Debug` completes without a test host stack overflow. +- [ ] Windows host smoke test completed. +- [ ] Windows Launcher smoke test completed. +- [ ] Settings window FluentAvalonia 3 smoke test completed. +- [ ] Component editor Material smoke test completed. +- [ ] BrowserWidget navigation/refresh/page activation smoke test completed. +- [ ] WebView2 missing-runtime diagnostic smoke test completed. diff --git a/.trae/specs/avalonia-12-migration/spec.md b/.trae/specs/avalonia-12-migration/spec.md index 74fdb1b..37206b8 100644 --- a/.trae/specs/avalonia-12-migration/spec.md +++ b/.trae/specs/avalonia-12-migration/spec.md @@ -1,63 +1,49 @@ -# Avalonia 12 迁移规格 +# Avalonia 12 Full Stack Migration -## Why +## Summary -Avalonia 12 带来性能改进(SkiaSharp 3.0、编译绑定默认开启)、新的窗口装饰体系(WindowDrawnDecorations)和更简洁的 API 设计。项目当前已升级包引用,但存在 18 个编译错误和若干过时 API 警告,需要系统性修复以确保构建通过。 +LanMountainDesktop has moved its desktop stack to the Avalonia 12 baseline. The migration covers the main host, Launcher, Plugin SDK, plugin runtime loading policy, official WebView usage, ClassIsland Markdown, FluentAvalonia, FluentIcons, and Material-related dependencies. -## What Changes +## Requirements -- **BREAKING**: 移除 `ExtendClientAreaChromeHints` 和 `SystemDecorations` 的使用,迁移到 `WindowDecorations` -- **BREAKING**: 移除 `BindingPlugins.DataValidators` 的使用(v12 已移除绑定插件体系) -- **BREAKING**: 替换 `IClipboard.SetTextAsync` 为 `ClipboardExtensions.SetTextAsync` -- **BREAKING**: 更新 `Bitmap.CopyPixels` 调用签名(移除 `AlphaFormat` 参数) -- **BREAKING**: 替换 `FluentIcons.Avalonia.SymbolIconSource` 为 v3 等效 API -- 修复 `MainWindow.ComponentSystem.cs` 和 `MainWindow.DesktopPaging.cs` 中缺失的字段/变量 -- 批量替换 `TextBox.Watermark` → `PlaceholderText` +### Requirement: Centralized Avalonia 12 dependency baseline -## Impact +The solution SHALL use central package management for direct Avalonia-facing projects and keep the core UI dependency baseline on Avalonia `12.0.1`. -- 受影响代码: - - `LanMountainDesktop/Views/SettingsWindow.axaml.cs` - - `LanMountainDesktop/Views/ComponentEditorWindow.axaml.cs` - - `LanMountainDesktop/Views/MainWindow.ComponentSystem.cs` - - `LanMountainDesktop/Views/MainWindow.DesktopPaging.cs` - - `LanMountainDesktop/App.axaml.cs` - - `LanMountainDesktop/Views/Components/DesktopComponentFailureView.cs` - - `LanMountainDesktop/Services/MonetColorService.cs` - - 13 个 `.axaml` 文件(`SystemDecorations` → `WindowDecorations`) - - 7 个 `.cs` 文件 + 7 个 `.axaml` 文件(`Watermark` → `PlaceholderText`) -- 受影响规格:无现有规格直接关联 +Required package baseline: -## ADDED Requirements +- `Avalonia*` `12.0.1` +- `Avalonia.Controls.WebView` `12.0.0` +- `ClassIsland.Markdown.Avalonia` `12.0.0` +- `FluentAvaloniaUI` `3.0.0-preview1` +- `FluentIcons.Avalonia` `2.1.325` +- `Material.Avalonia` `3.16.0` +- `Material.Icons.Avalonia` `3.0.2` -### Requirement: 窗口装饰 API 迁移 -系统 SHALL 使用 Avalonia 12 的 `WindowDecorations` 属性替代已移除的 `SystemDecorations` 和 `ExtendClientAreaChromeHints`。 +### Requirement: Official WebView -#### Scenario: SettingsWindow 无边框模式 -- **WHEN** `ApplyChromeMode(false)` 被调用 -- **THEN** `WindowDecorations = WindowDecorations.BorderOnly` 且 `ExtendClientAreaToDecorationsHint = true` +The host SHALL use `Avalonia.Controls.NativeWebView` for the browser widget and SHALL NOT reference `WebView.Avalonia`, `AvaloniaWebView`, or `.UseDesktopWebView()`. -#### Scenario: SettingsWindow 系统 Chrome 模式 -- **WHEN** `ApplyChromeMode(true)` 被调用 -- **THEN** `WindowDecorations = WindowDecorations.Full` 且 `ExtendClientAreaToDecorationsHint = true` +Windows WebView2 user data configuration SHALL be supplied through `EnvironmentRequested` using `WindowsWebView2EnvironmentRequestedEventArgs.UserDataFolder`. -### Requirement: 剪贴板 API 迁移 -系统 SHALL 使用 Avalonia 12 的 `ClipboardExtensions.SetTextAsync` 替代已移除的 `IClipboard.SetTextAsync`。 +### Requirement: Plugin SDK v5 -### Requirement: Bitmap.CopyPixels 签名更新 -系统 SHALL 使用新的 `CopyPixels` 签名,不再传入 `AlphaFormat` 参数。 +The Plugin SDK API baseline SHALL be `5.0.0`. SDK v4 plugins are treated as incompatible until rebuilt. -### Requirement: FluentIcons v3 API 适配 -系统 SHALL 使用 `FluentIcons.Avalonia.FluentIcon` 替代已移除的 `SymbolIconSource`。 +The SDK SHALL keep the existing public UI extension shape, including `SettingsPageBase` and Avalonia `Control` based desktop components. -## MODIFIED Requirements +### Requirement: Launcher data location stability -### Requirement: 编译绑定验证 -- **修改前**:`BindingPlugins.DataValidators.RemoveAt(0)` 移除默认数据注解验证插件 -- **修改后**:v12 默认禁用数据注解验证插件,无需手动移除 +Launcher data location configuration SHALL be read from a fixed bootstrap Launcher data directory so resolving the selected data root cannot recursively require resolving itself. -## REMOVED Requirements +### Requirement: OOBE state compatibility -### Requirement: ExtendClientAreaChromeHints 配置 -**Reason**: Avalonia 12 移除此属性,由 `WindowDecorations` 统一管理 -**Migration**: 删除所有 `ExtendClientAreaChromeHints` 赋值代码 +The Launcher SHALL read current OOBE state from the resolved `Launcher/state` directory and SHALL continue to migrate the legacy `.launcher/state/first_run_completed` marker. + +## Acceptance + +- `dotnet build LanMountainDesktop.slnx -c Debug` completes with 0 errors. +- `OobeStateServiceTests` pass. +- Full `dotnet test LanMountainDesktop.slnx -c Debug` no longer aborts from `DataLocationResolver` recursion. +- Plugin template defaults to SDK package version `5.0.0` and manifest `apiVersion` `5.0.0`. +- Current developer documentation points to SDK v5 and Avalonia 12. diff --git a/.trae/specs/avalonia-12-migration/tasks.md b/.trae/specs/avalonia-12-migration/tasks.md index dee08fc..c30c775 100644 --- a/.trae/specs/avalonia-12-migration/tasks.md +++ b/.trae/specs/avalonia-12-migration/tasks.md @@ -1,39 +1,18 @@ # Tasks -- [ ] Task 1: 修复窗口装饰 API(Phase 1) - - [x] SubTask 1.1: 重写 `SettingsWindow.ApplyChromeMode()` 移除 `ExtendClientAreaChromeHints` - - [x] SubTask 1.2: 重写 `ComponentEditorWindow.ApplyChromeMode()` 移除 `ExtendClientAreaChromeHints` - - [x] SubTask 1.3: 批量替换所有 `.axaml` 中的 `SystemDecorations` → `WindowDecorations` - - [ ] SubTask 1.4: 验证构建错误减少 - -- [ ] Task 2: 修复 MainWindow 编译错误(Phase 2) - - [ ] SubTask 2.1: 修复 `MainWindow.ComponentSystem.cs` 中 `centerLeft` 和 `positions` 未定义错误 - - [ ] SubTask 2.2: 修复 `MainWindow.DesktopPaging.cs` 中 `child` 和 `_isThreeFingerOrRightDragSwipeActive` 未定义错误 - - [ ] SubTask 2.3: 验证构建错误减少 - -- [ ] Task 3: 修复 Avalonia 12 API 变更(Phase 3) - - [ ] SubTask 3.1: 移除 `App.axaml.cs` 中 `BindingPlugins.DataValidators` 代码 - - [ ] SubTask 3.2: 替换 `DesktopComponentFailureView.cs` 中 `IClipboard.SetTextAsync` 为 `ClipboardExtensions.SetTextAsync` - - [ ] SubTask 3.3: 更新 `MonetColorService.cs` 中 `Bitmap.CopyPixels` 调用签名 - - [ ] SubTask 3.4: 验证构建错误减少 - -- [ ] Task 4: 修复第三方库变更(Phase 4) - - [ ] SubTask 4.1: 替换 `SettingsWindow.axaml.cs` 中 `FluentIcons.Avalonia.SymbolIconSource` 为 `FluentIcon` - - [ ] SubTask 4.2: 验证构建错误减少 - -- [ ] Task 5: 清理过时属性(Phase 5) - - [ ] SubTask 5.1: 批量替换 `.cs` 文件中 `Watermark` → `PlaceholderText` - - [ ] SubTask 5.2: 批量替换 `.axaml` 文件中 `Watermark` → `PlaceholderText` - - [ ] SubTask 5.3: 验证无过时警告 - -- [ ] Task 6: 最终验证 - - [ ] SubTask 6.1: `dotnet build LanMountainDesktop.slnx -c Debug` 0 errors - - [ ] SubTask 6.2: `dotnet test LanMountainDesktop.slnx -c Debug` 通过 - -# Task Dependencies - -- Task 2 不依赖 Task 1(可并行) -- Task 3 不依赖 Task 1/2(可并行) -- Task 4 不依赖 Task 1/2/3(可并行) -- Task 5 依赖 Task 1/2/3/4(低优先级,最后执行) -- Task 6 依赖所有前置任务 +- [x] Centralize Avalonia 12 package versions in `Directory.Packages.props`. +- [x] Move the host, Launcher, Plugin SDK, DesktopHost, Shared.Contracts, and Avalonia-facing projects onto central package versions. +- [x] Replace third-party `WebView.Avalonia` usage with official `NativeWebView`. +- [x] Configure WebView2 user data through `EnvironmentRequested`. +- [x] Move FluentAvalonia usages to the FA3 control names and package baseline. +- [x] Move FluentIcons usage to `FluentIcons.Avalonia` and remove the old `.Fluent` package. +- [x] Update Plugin SDK package version and API baseline to `5.0.0`. +- [x] Update plugin runtime shared assembly policy for Avalonia 12 / FluentAvalonia / FluentIcons / Material. +- [x] Fix Avalonia 12 compile breaks in window chrome, binding plugin access, clipboard, bitmap copy, and icon source usage. +- [x] Fix Launcher data location recursion by using a fixed bootstrap config path. +- [x] Fix OOBE state tests and legacy marker compatibility. +- [x] Update PluginTemplate defaults to SDK v5. +- [x] Add SDK v5 migration documentation. +- [x] Update current docs from SDK v4 / Avalonia 11 examples to SDK v5 / Avalonia 12. +- [x] Run full solution tests and record any remaining non-upgrade failures. +- [ ] Perform Windows manual smoke test for host, Launcher, settings, component editor, BrowserWidget, and WebView2 missing-runtime handling. diff --git a/AGENTS.md b/AGENTS.md index 211433c..792e090 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -74,7 +74,7 @@ dotnet test LanMountainDesktop.slnx -c Debug - SDK 公共 API 以 `LanMountainDesktop.PluginSdk/` 为准 - 共享契约以 `LanMountainDesktop.Shared.Contracts/` 为准 - market 数据来源默认是兄弟仓库 `..\\LanAirApp` -- 迁移或 breaking change 优先同步 `docs/PLUGIN_SDK_V4_MIGRATION.md` +- 迁移或 breaking change 优先同步 `docs/PLUGIN_SDK_V5_MIGRATION.md` ### 设置与主题 @@ -91,6 +91,6 @@ dotnet test LanMountainDesktop.slnx -c Debug - 视觉规范:`docs/VISUAL_SPEC.md` - 圆角规范:`docs/CORNER_RADIUS_SPEC.md` - 生态边界:`docs/ECOSYSTEM_BOUNDARIES.md` -- SDK v4 迁移:`docs/PLUGIN_SDK_V4_MIGRATION.md` +- SDK v5 迁移:`docs/PLUGIN_SDK_V5_MIGRATION.md` 如果多个文档都提到同一件事,以 `docs/ai/DOC_SOURCES.md` 列出的权威来源为准。 diff --git a/LanMountainDesktop.Launcher/Services/DataLocationResolver.cs b/LanMountainDesktop.Launcher/Services/DataLocationResolver.cs index 860b884..67e573b 100644 --- a/LanMountainDesktop.Launcher/Services/DataLocationResolver.cs +++ b/LanMountainDesktop.Launcher/Services/DataLocationResolver.cs @@ -32,6 +32,11 @@ internal sealed class DataLocationResolver /// public string DefaultPortableDataPath => Path.Combine(_appRoot, "AppData"); + private string ResolveBootstrapLauncherDataPath() + { + return Path.Combine(_defaultSystemDataPath, LauncherFolderName); + } + /// /// 检查是否允许便携模式(应用目录是否可写) /// @@ -56,6 +61,11 @@ internal sealed class DataLocationResolver public string ResolveDataRoot() { var config = LoadConfig(); + return ResolveDataRoot(config); + } + + private string ResolveDataRoot(DataLocationConfig? config) + { if (config is null) { return _defaultSystemDataPath; @@ -65,7 +75,7 @@ internal sealed class DataLocationResolver { var portablePath = !string.IsNullOrWhiteSpace(config.PortableDataPath) ? config.PortableDataPath - : _defaultSystemDataPath; + : DefaultPortableDataPath; return Path.GetFullPath(portablePath); } @@ -95,7 +105,7 @@ internal sealed class DataLocationResolver /// public string ResolveConfigPath() { - return Path.Combine(ResolveLauncherDataPath(), ConfigFileName); + return Path.Combine(ResolveBootstrapLauncherDataPath(), ConfigFileName); } /// @@ -151,7 +161,7 @@ internal sealed class DataLocationResolver { try { - var launcherPath = ResolveLauncherDataPath(); + var launcherPath = ResolveBootstrapLauncherDataPath(); Directory.CreateDirectory(launcherPath); var configPath = ResolveConfigPath(); @@ -182,8 +192,9 @@ internal sealed class DataLocationResolver // 先创建目录结构 try { - Directory.CreateDirectory(ResolveLauncherDataPath()); - Directory.CreateDirectory(ResolveDesktopDataPath()); + var resolvedDataRoot = ResolveDataRoot(config); + Directory.CreateDirectory(Path.Combine(resolvedDataRoot, LauncherFolderName)); + Directory.CreateDirectory(Path.Combine(resolvedDataRoot, DesktopFolderName)); } catch (Exception ex) { diff --git a/LanMountainDesktop.Launcher/Services/OobeStateService.cs b/LanMountainDesktop.Launcher/Services/OobeStateService.cs index 9ecca79..a7c4ca3 100644 --- a/LanMountainDesktop.Launcher/Services/OobeStateService.cs +++ b/LanMountainDesktop.Launcher/Services/OobeStateService.cs @@ -9,6 +9,7 @@ internal sealed class OobeStateService private readonly string _stateDirectory; private readonly string _statePath; + private readonly string _legacyStatePath; private readonly string _legacyMarkerPath; private readonly LauncherExecutionSnapshot _executionSnapshot; @@ -25,7 +26,13 @@ internal sealed class OobeStateService : Path.GetFullPath(stateRootOverride); _stateDirectory = Path.Combine(stateRoot, "Launcher", "state"); _statePath = Path.Combine(_stateDirectory, "oobe-state.json"); - _legacyMarkerPath = Path.Combine(_stateDirectory, "first_run_completed"); + + var legacyRoot = string.IsNullOrWhiteSpace(stateRootOverride) + ? Path.GetFullPath(appRoot) + : Path.GetFullPath(stateRootOverride); + var legacyStateDirectory = Path.Combine(legacyRoot, ".launcher", "state"); + _legacyStatePath = Path.Combine(legacyStateDirectory, "oobe-state.json"); + _legacyMarkerPath = Path.Combine(legacyStateDirectory, "first_run_completed"); } public OobeLaunchDecision Evaluate(CommandContext context) @@ -100,14 +107,12 @@ internal sealed class OobeStateService var migratedLegacyMarker = false; if (File.Exists(_statePath)) { - using var stream = File.OpenRead(_statePath); - var state = JsonSerializer.Deserialize(stream, AppJsonContext.Default.OobeStateFile); - if (state is null || state.SchemaVersion <= 0 || string.IsNullOrWhiteSpace(state.CompletedAtUtc)) - { - return BuildUnavailableDecision(context, "OOBE state file is invalid."); - } + return EvaluateStateFile(context, _statePath, migratedLegacyState: false); + } - return BuildDecision(context, OobeStateStatus.Completed, shouldShowOobe: false, migratedLegacyMarker: false); + if (File.Exists(_legacyStatePath)) + { + return EvaluateStateFile(context, _legacyStatePath, migratedLegacyState: false); } if (File.Exists(_legacyMarkerPath)) @@ -140,6 +145,18 @@ internal sealed class OobeStateService return result.Success; } + private OobeLaunchDecision EvaluateStateFile(CommandContext context, string statePath, bool migratedLegacyState) + { + using var stream = File.OpenRead(statePath); + var state = JsonSerializer.Deserialize(stream, AppJsonContext.Default.OobeStateFile); + if (state is null || state.SchemaVersion <= 0 || string.IsNullOrWhiteSpace(state.CompletedAtUtc)) + { + return BuildUnavailableDecision(context, "OOBE state file is invalid."); + } + + return BuildDecision(context, OobeStateStatus.Completed, shouldShowOobe: false, migratedLegacyMarker: migratedLegacyState); + } + private void TryDeleteLegacyMarker() { try diff --git a/LanMountainDesktop.PluginIsolation.Contracts/LanMountainDesktop.PluginIsolation.Contracts.csproj b/LanMountainDesktop.PluginIsolation.Contracts/LanMountainDesktop.PluginIsolation.Contracts.csproj index cb3f84f..9f32f8f 100644 --- a/LanMountainDesktop.PluginIsolation.Contracts/LanMountainDesktop.PluginIsolation.Contracts.csproj +++ b/LanMountainDesktop.PluginIsolation.Contracts/LanMountainDesktop.PluginIsolation.Contracts.csproj @@ -3,7 +3,7 @@ net10.0 enable enable - 1.0.0 + 5.0.0 LanMountainDesktop.PluginIsolation.Contracts true LanMountainDesktop diff --git a/LanMountainDesktop.PluginIsolation.Ipc/LanMountainDesktop.PluginIsolation.Ipc.csproj b/LanMountainDesktop.PluginIsolation.Ipc/LanMountainDesktop.PluginIsolation.Ipc.csproj index ae81edd..c3135cc 100644 --- a/LanMountainDesktop.PluginIsolation.Ipc/LanMountainDesktop.PluginIsolation.Ipc.csproj +++ b/LanMountainDesktop.PluginIsolation.Ipc/LanMountainDesktop.PluginIsolation.Ipc.csproj @@ -3,7 +3,7 @@ net10.0 enable enable - 1.0.0 + 5.0.0 LanMountainDesktop.PluginIsolation.Ipc true LanMountainDesktop diff --git a/LanMountainDesktop.PluginSdk/LanMountainDesktop.PluginSdk.csproj b/LanMountainDesktop.PluginSdk/LanMountainDesktop.PluginSdk.csproj index c276da6..ce78b10 100644 --- a/LanMountainDesktop.PluginSdk/LanMountainDesktop.PluginSdk.csproj +++ b/LanMountainDesktop.PluginSdk/LanMountainDesktop.PluginSdk.csproj @@ -4,7 +4,7 @@ net10.0 enable enable - 5.0.0-preview1 + 5.0.0 LanMountainDesktop.PluginSdk true LanMountainDesktop diff --git a/LanMountainDesktop.PluginSdk/README.md b/LanMountainDesktop.PluginSdk/README.md index a69adbd..0b8db80 100644 --- a/LanMountainDesktop.PluginSdk/README.md +++ b/LanMountainDesktop.PluginSdk/README.md @@ -16,7 +16,7 @@ Official SDK package for LanMountainDesktop plugins. ```xml - + ``` diff --git a/LanMountainDesktop.PluginTemplate/content/.template.config/template.json b/LanMountainDesktop.PluginTemplate/content/.template.config/template.json index daa78be..2dd0cf4 100644 --- a/LanMountainDesktop.PluginTemplate/content/.template.config/template.json +++ b/LanMountainDesktop.PluginTemplate/content/.template.config/template.json @@ -47,7 +47,7 @@ "pluginSdkVersion": { "type": "parameter", "datatype": "text", - "defaultValue": "4.0.2", + "defaultValue": "5.0.0", "description": "LanMountainDesktop.PluginSdk package version.", "replaces": "__PLUGIN_SDK_VERSION__" } diff --git a/LanMountainDesktop.PluginTemplate/content/plugin.json b/LanMountainDesktop.PluginTemplate/content/plugin.json index 6ca9f8b..1ffed34 100644 --- a/LanMountainDesktop.PluginTemplate/content/plugin.json +++ b/LanMountainDesktop.PluginTemplate/content/plugin.json @@ -4,7 +4,7 @@ "description": "__PLUGIN_DESCRIPTION__", "author": "__PLUGIN_AUTHOR__", "version": "1.0.0", - "apiVersion": "4.0.2", + "apiVersion": "5.0.0", "entranceAssembly": "LanMountainDesktop.PluginTemplate.dll", "sharedContracts": [], "runtime": { diff --git a/LanMountainDesktop.Shared.Contracts/LanMountainDesktop.Shared.Contracts.csproj b/LanMountainDesktop.Shared.Contracts/LanMountainDesktop.Shared.Contracts.csproj index 5f78f01..de5af00 100644 --- a/LanMountainDesktop.Shared.Contracts/LanMountainDesktop.Shared.Contracts.csproj +++ b/LanMountainDesktop.Shared.Contracts/LanMountainDesktop.Shared.Contracts.csproj @@ -3,7 +3,7 @@ net10.0 enable enable - 0.0.0-dev + 5.0.0 LanMountainDesktop.Shared.Contracts true LanMountainDesktop diff --git a/LanMountainDesktop.Shared.IPC/LanMountainDesktop.Shared.IPC.csproj b/LanMountainDesktop.Shared.IPC/LanMountainDesktop.Shared.IPC.csproj index 0b13646..87b60e1 100644 --- a/LanMountainDesktop.Shared.IPC/LanMountainDesktop.Shared.IPC.csproj +++ b/LanMountainDesktop.Shared.IPC/LanMountainDesktop.Shared.IPC.csproj @@ -3,7 +3,7 @@ net10.0 enable enable - 1.0.0 + 5.0.0 LanMountainDesktop.Shared.IPC true LanMountainDesktop diff --git a/LanMountainDesktop.Tests/ComponentSettingsServiceTests.cs b/LanMountainDesktop.Tests/ComponentSettingsServiceTests.cs index f752fa7..516b9fe 100644 --- a/LanMountainDesktop.Tests/ComponentSettingsServiceTests.cs +++ b/LanMountainDesktop.Tests/ComponentSettingsServiceTests.cs @@ -1,4 +1,3 @@ -using System.Text.Json; using LanMountainDesktop.Models; using LanMountainDesktop.Services; using Xunit; @@ -34,10 +33,14 @@ public sealed class ComponentSettingsServiceTests Assert.Equal("Sweep", snapshot.DesktopClockSecondHandMode); Assert.Single(snapshot.ImportedClassSchedules); - using var document = JsonDocument.Parse(File.ReadAllText(sandbox.SettingsPath)); - Assert.True(document.RootElement.TryGetProperty("defaultSettings", out var defaultSettings)); - Assert.Equal("Sweep", defaultSettings.GetProperty("desktopClockSecondHandMode").GetString()); - Assert.False(document.RootElement.TryGetProperty("DesktopClockSecondHandMode", out _)); + Assert.True(File.Exists(sandbox.DatabasePath)); + Assert.False(File.Exists(sandbox.SettingsPath)); + Assert.True(File.Exists(sandbox.SettingsBackupPath)); + + ComponentSettingsService.ResetCacheForTests(); + var reloadedService = sandbox.CreateService(); + var reloaded = reloadedService.Load(); + Assert.Equal("Sweep", reloaded.DesktopClockSecondHandMode); } [Fact] @@ -72,11 +75,16 @@ public sealed class ComponentSettingsServiceTests Assert.Equal("Sweep", snapshot.DesktopClockSecondHandMode); Assert.True(pluginSettings.SampleFlag); - using var document = JsonDocument.Parse(File.ReadAllText(sandbox.SettingsPath)); - Assert.True(document.RootElement.TryGetProperty("instanceSettings", out var instanceSettings)); - Assert.True(instanceSettings.TryGetProperty("DesktopClock::clock-2x2", out var clockSettings)); - Assert.Equal("Sweep", clockSettings.GetProperty("desktopClockSecondHandMode").GetString()); - Assert.False(document.RootElement.TryGetProperty("InstanceSettings", out _)); + Assert.True(File.Exists(sandbox.DatabasePath)); + Assert.False(File.Exists(sandbox.SettingsPath)); + Assert.True(File.Exists(sandbox.SettingsBackupPath)); + + ComponentSettingsService.ResetCacheForTests(); + var reloadedService = sandbox.CreateService(); + var reloadedSnapshot = reloadedService.LoadForComponent("DesktopClock", "clock-2x2"); + var reloadedPluginSettings = reloadedService.LoadPluginSettings("DesktopClock", "clock-2x2"); + Assert.Equal("Sweep", reloadedSnapshot.DesktopClockSecondHandMode); + Assert.True(reloadedPluginSettings.SampleFlag); } [Fact] @@ -132,12 +140,7 @@ public sealed class ComponentSettingsServiceTests Assert.True(pluginSettings.SampleFlag); Assert.Equal("schedule-settings", pluginSettings.Title); - using var document = JsonDocument.Parse(File.ReadAllText(sandbox.SettingsPath)); - Assert.True(document.RootElement.TryGetProperty("instanceSettings", out var instanceSettings)); - Assert.True(instanceSettings.TryGetProperty("DesktopClock::clock-2x2", out _)); - Assert.True(instanceSettings.TryGetProperty("DesktopClassSchedule::class-schedule-2x2", out _)); - Assert.True(document.RootElement.TryGetProperty("pluginSettings", out var pluginSettingsNode)); - Assert.True(pluginSettingsNode.TryGetProperty("DesktopClassSchedule::class-schedule-2x2", out _)); + Assert.True(File.Exists(sandbox.DatabasePath)); } private sealed class ComponentSettingsSandbox : IDisposable @@ -155,6 +158,10 @@ public sealed class ComponentSettingsServiceTests public string SettingsPath => Path.Combine(_directoryPath, "component-settings.json"); + public string SettingsBackupPath => $"{SettingsPath}.migrated.bak"; + + public string DatabasePath => Path.Combine(_directoryPath, "component-state.db"); + public ComponentSettingsService CreateService() { return new ComponentSettingsService(_directoryPath); diff --git a/LanMountainDesktop.Tests/OobeStateServiceTests.cs b/LanMountainDesktop.Tests/OobeStateServiceTests.cs index 99b3be3..5729c1a 100644 --- a/LanMountainDesktop.Tests/OobeStateServiceTests.cs +++ b/LanMountainDesktop.Tests/OobeStateServiceTests.cs @@ -118,7 +118,7 @@ public sealed class OobeStateServiceTests : IDisposable executionSnapshot: executionSnapshot ?? new LauncherExecutionSnapshot(false, "tester", "S-1-5-test")); } - private string GetStatePath() => Path.Combine(_tempRoot, ".launcher", "state", "oobe-state.json"); + private string GetStatePath() => Path.Combine(_tempRoot, "Launcher", "state", "oobe-state.json"); private string GetLegacyMarkerPath() => Path.Combine(_tempRoot, ".launcher", "state", "first_run_completed"); } diff --git a/README.md b/README.md index 7541e91..a5414a0 100644 --- a/README.md +++ b/README.md @@ -96,9 +96,9 @@ dotnet new install LanMountainDesktop.PluginTemplate dotnet new lmd-plugin -n MyPlugin ``` -- **Plugin SDK**: `LanMountainDesktop.PluginSdk` (API 4.0.1) +- **Plugin SDK**: `LanMountainDesktop.PluginSdk` (API 5.0.0) - **共享契约**: `LanMountainDesktop.Shared.Contracts` -- **迁移指南**: [PLUGIN_SDK_V4_MIGRATION.md](docs/PLUGIN_SDK_V4_MIGRATION.md) +- **迁移指南**: [PLUGIN_SDK_V5_MIGRATION.md](docs/PLUGIN_SDK_V5_MIGRATION.md) ## 项目结构 diff --git a/docs/PLUGIN_DEVELOPMENT.md b/docs/PLUGIN_DEVELOPMENT.md index e0b71a0..9967642 100644 --- a/docs/PLUGIN_DEVELOPMENT.md +++ b/docs/PLUGIN_DEVELOPMENT.md @@ -63,11 +63,11 @@ MyAwesomePlugin/ ### 插件 SDK 版本 -当前 SDK 版本: **4.0.1** +当前 SDK 版本: **5.0.0** ```xml - - + + ``` ### 插件清单 (plugin.json) @@ -175,9 +175,9 @@ public class Plugin : IPlugin - - - + + + @@ -680,7 +680,7 @@ if (!url.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) ## 相关文档 -- [Plugin SDK v4 迁移指南](PLUGIN_SDK_V4_MIGRATION.md) +- [Plugin SDK v5 迁移指南](PLUGIN_SDK_V5_MIGRATION.md) - [组件开发指南](COMPONENT_DEVELOPMENT.md) - [API 参考](API_REFERENCE.md) - [架构文档](ARCHITECTURE.md) diff --git a/docs/PLUGIN_SDK_V5_MIGRATION.md b/docs/PLUGIN_SDK_V5_MIGRATION.md new file mode 100644 index 0000000..bd4cc79 --- /dev/null +++ b/docs/PLUGIN_SDK_V5_MIGRATION.md @@ -0,0 +1,34 @@ +# Plugin SDK v5 Migration Guide + +Plugin SDK v5 is the Avalonia 12 compatibility baseline for LanMountainDesktop plugins. + +## What Changed + +- Rebuild plugins against `LanMountainDesktop.PluginSdk` `5.0.0`. +- Set `plugin.json` `apiVersion` to `5.0.0`. +- Target `net10.0` and use Avalonia `12.0.1` compatible UI dependencies. +- Use `FluentAvaloniaUI` `3.0.0-preview1` and `FluentIcons.Avalonia` `2.1.325` when a plugin directly references those packages. + +## Compatibility + +SDK v5 is a binary breaking change because the SDK exposes Avalonia UI types such as `Control`, `UserControl`, and `SettingsPageBase`. Plugins built for SDK v4 must be rebuilt and republished for SDK v5. + +The host does not provide an Avalonia 11 / Avalonia 12 dual UI stack. The public extension entry points remain the same: custom settings pages still derive from `SettingsPageBase`, and desktop components still provide Avalonia controls through the existing registration APIs. + +## Minimal Package Update + +```xml + + + +``` + +```json +{ + "apiVersion": "5.0.0" +} +``` + +## Validation + +After updating package versions and rebuilding the plugin, verify that the generated `.laapp` contains the rebuilt assembly, `plugin.json`, and `.deps.json` next to the plugin entry assembly. diff --git a/docs/PRODUCT.md b/docs/PRODUCT.md index be882e7..7fb3aca 100644 --- a/docs/PRODUCT.md +++ b/docs/PRODUCT.md @@ -39,7 +39,7 @@ ### 当前阶段 - 产品版本:`1.0.0` -- Plugin SDK API 基线:`4.0.1` +- Plugin SDK API 基线:`5.0.0` - 当前重点:持续完善宿主体验、设置页体验、组件能力与插件生态 - 近期需求入口:以 `.trae/specs/` 中的 feature spec 为准 @@ -59,4 +59,4 @@ LanMountainDesktop is a cross-platform desktop enhancement product built with Avalonia UI and .NET 10. It targets students, office users, and customization-focused users who want a programmable desktop surface for information, tools, and plugin-driven extensions. -This repository is the source of truth for the desktop host, plugin runtime, Plugin SDK, shared contracts, and core appearance/settings infrastructure. The current product version is `1.0.0`, and the active Plugin SDK baseline in this repository is `4.0.1`. +This repository is the source of truth for the desktop host, plugin runtime, Plugin SDK, shared contracts, and core appearance/settings infrastructure. The current product version is `1.0.0`, and the active Plugin SDK baseline in this repository is `5.0.0`. diff --git a/docs/Plugins develop/01-快速开始/02-三分钟创建第一个插件.md b/docs/Plugins develop/01-快速开始/02-三分钟创建第一个插件.md index 3107848..f79e707 100644 --- a/docs/Plugins develop/01-快速开始/02-三分钟创建第一个插件.md +++ b/docs/Plugins develop/01-快速开始/02-三分钟创建第一个插件.md @@ -40,7 +40,7 @@ cd MyFirstPlugin "description": "这是一个测试插件", "author": "你的名字", "version": "1.0.0", - "apiVersion": "4.0.1", + "apiVersion": "5.0.0", "entranceAssembly": "MyFirstPlugin.dll", "sharedContracts": [] } diff --git a/docs/Plugins develop/01-快速开始/03-插件项目结构详解.md b/docs/Plugins develop/01-快速开始/03-插件项目结构详解.md index c355107..7796fd5 100644 --- a/docs/Plugins develop/01-快速开始/03-插件项目结构详解.md +++ b/docs/Plugins develop/01-快速开始/03-插件项目结构详解.md @@ -35,7 +35,7 @@ MyPlugin/ "description": "这是一个示例插件", "author": "作者名称", "version": "1.0.0", - "apiVersion": "4.0.1", + "apiVersion": "5.0.0", "entranceAssembly": "MyPlugin.dll", "sharedContracts": [], "website": "https://example.com", @@ -53,7 +53,7 @@ MyPlugin/ | `description` | ✅ | 简短描述 | `显示实时天气信息` | | `author` | ✅ | 作者名称 | `张三` | | `version` | ✅ | 版本号(语义化版本) | `1.0.0` | -| `apiVersion` | ✅ | SDK API 版本 | `4.0.1` | +| `apiVersion` | ✅ | SDK API 版本 | `5.0.0` | | `entranceAssembly` | ✅ | 入口程序集文件名 | `MyPlugin.dll` | | `sharedContracts` | ✅ | 共享契约类型列表 | `[]` | | `website` | ❌ | 项目网站 | `https://github.com/...` | @@ -74,7 +74,7 @@ MyPlugin/ ⚠️ **apiVersion 字段规则:** - 必须与引用的 SDK 版本兼容 -- 当前最新版本:`4.0.1` +- 当前最新版本:`5.0.0` - 不兼容时宿主将拒绝加载插件 --- @@ -96,7 +96,7 @@ MyPlugin/ - + @@ -122,7 +122,7 @@ MyPlugin/ ### SDK 引用 ```xml - + ``` ⚠️ **版本必须匹配:** diff --git a/docs/Plugins develop/02-核心概念与原理/01-插件生命周期.md b/docs/Plugins develop/02-核心概念与原理/01-插件生命周期.md index 36148da..8ae01ef 100644 --- a/docs/Plugins develop/02-核心概念与原理/01-插件生命周期.md +++ b/docs/Plugins develop/02-核心概念与原理/01-插件生命周期.md @@ -213,7 +213,7 @@ public override void Initialize(HostBuilderContext context, IServiceCollection s - 关闭阑山桌面 **当前限制:** -- SDK v4 暂无显式的卸载回调方法 +- SDK v5 暂无显式的卸载回调方法 - 资源释放依赖 .NET 垃圾回收 - 建议: - 使用 `IDisposable` 模式管理资源 diff --git a/docs/Plugins develop/07-发布与运营/01-插件打包规范.md b/docs/Plugins develop/07-发布与运营/01-插件打包规范.md index 5b99533..daa8551 100644 --- a/docs/Plugins develop/07-发布与运营/01-插件打包规范.md +++ b/docs/Plugins develop/07-发布与运营/01-插件打包规范.md @@ -33,7 +33,7 @@ MyPlugin.laapp "description": "插件描述", "author": "作者名", "version": "1.0.0", - "apiVersion": "4.0.1", + "apiVersion": "5.0.0", "entranceAssembly": "MyPlugin.dll", "sharedContracts": [] } @@ -61,7 +61,7 @@ MyPlugin.laapp - + diff --git a/docs/ai/DOC_SOURCES.md b/docs/ai/DOC_SOURCES.md index 46b48d8..92d14ed 100644 --- a/docs/ai/DOC_SOURCES.md +++ b/docs/ai/DOC_SOURCES.md @@ -20,7 +20,7 @@ | 视觉规范 | `docs/VISUAL_SPEC.md` | 颜色、语义资源、玻璃层级 | | 圆角规范 | `docs/CORNER_RADIUS_SPEC.md` | 圆角层级与动态规则 | | 插件生态边界 | `docs/ECOSYSTEM_BOUNDARIES.md` | 仓库边界和 market 所属 | -| SDK v4 迁移 | `docs/PLUGIN_SDK_V4_MIGRATION.md` | Plugin SDK breaking changes | +| SDK v5 迁移 | `docs/PLUGIN_SDK_V5_MIGRATION.md` | Plugin SDK breaking changes | ## 已废弃来源