Skip to content

refactor!: remove ProjectService/EditorService/ExtensionProvider singletons#1982

Open
yuto-trd wants to merge 4 commits into
mainfrom
yuto-trd/reduce-global-service-locator
Open

refactor!: remove ProjectService/EditorService/ExtensionProvider singletons#1982
yuto-trd wants to merge 4 commits into
mainfrom
yuto-trd/reduce-global-service-locator

Conversation

@yuto-trd

@yuto-trd yuto-trd commented Jun 21, 2026

Copy link
Copy Markdown
Member

Description

Replaces the global ProjectService.Current, EditorService.Current, and ExtensionProvider.Current singletons with constructor injection threaded from the composition roots.

  • MainViewModel (and Beutl.PackageTools.UI's MainViewModel) own the instances and thread them into child view models, services, views (via DataContext), startup tasks (Startup -> LoadPrimitiveExtensionTask / RestoreLastProjectTask), settings pages, and the per-editor tier (EditViewModel -> DockHostViewModel / OutputViewModel / OutputService / property editors).
  • BeutlApiApplication receives its ExtensionProvider from the composition root instead of resolving ExtensionProvider.Current internally.
  • Editor contexts are created without globals: EditorExtension.TryCreateContext now receives a typed IEditorContextServices — a new host-services seam in Beutl.Extensibility exposing IExtensionProvider. ExtensionProvider implements IExtensionProvider.
  • The built-in tutorials receive their services through DefaultTutorialExtension's constructor (built by LoadPrimitiveExtensionTask); its static Instance is removed.

Affected areas

  • Beutl.Engine (rendering / scene / track)
  • Beutl.ProjectSystem (project / document persistence)
  • UI (Beutl.Editor, Beutl.Editor.Components, Beutl.Controls)
  • Beutl.Extensibility (plugin abstractions)
  • Beutl.NodeGraph (node editor)
  • Beutl.FFmpegIpc / Beutl.FFmpegWorker (media IPC boundary)
  • Beutl.Api (server API client)
  • Build / CI / docs only

Breaking changes

  1. EditorExtension.TryCreateContext gains an IEditorContextServices parameter:
    TryCreateContext(CoreObject obj, IEditorContextServices services, out IEditorContext? context).
    Implementations must add the parameter; those that need no host services may ignore it. Use services.ExtensionProvider (IExtensionProvider) to query other extensions.
  2. BeutlApiApplication(HttpClient) was replaced with BeutlApiApplication(HttpClient, ExtensionProvider). Callers must pass the provider from their composition root.
  3. ProjectService.Current, EditorService.Current, and ExtensionProvider.Current are removed. Obtain these services via constructor injection / IEditorContext.GetRequiredService<...> instead.

Test plan

  • dotnet build Beutl.slnx -> 0 errors.
  • dotnet test tests/Beutl.UnitTests/Beutl.UnitTests.csproj -f net10.0 -> 3670 passed, no regressions. (3 AutoSaveServiceTests cases fail only on Windows local: CoreSerializer.StoreToUri auto-creates the target directory, so a save to a "non-existent" path succeeds and emits no SaveError. On the Linux CI runner the root path is not creatable, so they pass. Pre-existing and unrelated to this change.)
  • New coverage: tests/Beutl.UnitTests/Api/ExtensionProviderTests.cs (new) and extended BeutlApiApplicationTests.cs.
  • dotnet format Beutl.slnx --verify-no-changes over the changed set -> clean (after restoring UTF-8 BOM on 5 files that lost it during editing).

Fixed issues / References

  • Project board: b-editor/projects/9 ("設計改善: グローバル singleton / service locator を減らす")
  • The earlier follow-up "continue moving remaining *.Current use sites toward composition-root injection" is completed by this PR (all three statics removed).

Summary by CodeRabbit

  • New Features

    • Added a host-provided extension provider contract so extension matching can be driven by the active editor session.
    • Editor extension context creation now uses session services for more reliable tool/page behavior.
  • Bug Fixes

    • Replaced global “current” service lookups with injected, instance-based session data across editor, tools, outputs, and startup/restore flows.
    • Improved extension/property matching and context setup when switching projects/tabs.
  • Tests

    • Added unit coverage for constructor validation and core extension-provider behaviors.

BeutlApiApplication now receives ExtensionProvider from the composition root instead of registering ExtensionProvider.Current directly.

This localizes global access to app entry points and lets tests construct an isolated provider. The app and package tools now pass their provider explicitly.

Refs: Project #9 "AI Review" item "設計改善: グローバル singleton / service locator を減らす"

BREAKING CHANGE: Beutl.Api's BeutlApiApplication constructor now requires an ExtensionProvider argument. Create or pass the desired provider from the composition root, for example ExtensionProvider.Current in the Beutl app.
@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR replaces static service access with injected ProjectService, EditorService, and ExtensionProvider instances across API contracts, shell wiring, editor/session plumbing, startup tasks, output flows, settings/package pages, and view/menu handlers. New tests cover the updated API contracts and provider behavior.

Changes

Service injection and editor context flow

Layer / File(s) Summary
Public API contracts
src/Beutl.Api/BeutlApiApplication.cs, src/Beutl.Api/Services/ExtensionProvider.cs, src/Beutl.Extensibility/EditorExtension.cs, src/Beutl.Extensibility/IEditorContextServices.cs, src/Beutl.Extensibility/IExtensionProvider.cs, src/Beutl/Services/ProjectService.cs, tests/Beutl.UnitTests/Api/*, tests/Beutl.UnitTests/Beutl.UnitTests.csproj, tests/PackageSample/SampleEditorExtension.cs
BeutlApiApplication, ExtensionProvider, and EditorExtension now expose injected service contracts, ProjectService.Current is removed, and tests cover the new constructor and provider behavior.
Shell composition
src/Beutl.PackageTools.UI/ViewModels/MainViewModel.cs, src/Beutl/App.axaml.cs, src/Beutl/ViewModels/EditorHostViewModel.cs, src/Beutl/ViewModels/MainViewModel.cs, src/Beutl/ViewModels/MenuBarViewModel*.cs, src/Beutl/ViewModels/CommandPaletteViewModel.cs, src/Beutl/Services/CommandPalette*.cs
MainViewModel now creates the shared project, editor, and extension services, passes them into shell view models and startup, and the menu bar and command palette read selection state from injected instances.
Editor infrastructure
src/Beutl/Services/EditorService.cs, src/Beutl/Services/EditorContextServices.cs, src/Beutl/Services/PropertyEditorService.cs, src/Beutl/Services/Adapters/*, src/Beutl/ViewModels/EditViewModel.cs, src/Beutl/ViewModels/Editors/BaseEditorViewModel.cs, src/Beutl/ViewModels/Editors/PropertiesEditorViewModel.cs
EditorService, EditViewModel, BaseEditorViewModel, PropertyEditorService, EditorContextServices, and the adapter factories now thread the session ExtensionProvider into tab activation and property-editor matching.
Editor consumers
src/Beutl/ViewModels/Editors/*EditorViewModel.cs, src/Beutl/ViewModels/Editors/PropertiesEditorViewModel.cs, src/Beutl/ViewModels/DockHostViewModel.cs, src/Beutl/ViewModels/EncoderSettingsViewModel.cs
Editor-specific view models and DockHostViewModel now build property editors with the session provider, and EncoderSettingsViewModel resolves encoders from the current editor context.
Startup and tutorials
src/Beutl/Services/StartupTasks/*, src/Beutl/Services/PrimitiveImpls/*, src/Beutl/Services/Tutorials/*
Startup tasks register primitive extensions with DefaultTutorialExtension, restore the last project through the injected ProjectService, and tutorial helpers and factory methods now accept EditorService and ProjectService.
Extensions and packages
src/Beutl/ViewModels/SettingsDialogViewModel.cs, src/Beutl/ViewModels/SettingsPages/*, src/Beutl/ViewModels/ExtensionsPageViewModel.cs, src/Beutl/ViewModels/ExtensionsPages/*, src/Beutl/ViewModels/ExtensionsPages/DiscoverPages/*, src/Beutl/Pages/SettingsPages/ExtensionsSettingsPage.axaml
Settings pages, extension-discovery pages, and package-operation view models now receive injected extension, project, and editor services for navigation, extension lists, and project-closed checks.
Output flow
src/Beutl/Services/OutputService.cs, src/Beutl/ViewModels/Tools/*, src/Beutl/ViewModels/Dialogs/AddOutputProfileViewModel.cs, src/Beutl/Views/Tools/*.axaml.cs
OutputService, OutputTabViewModel, OutputViewModel, AddOutputProfileViewModel, and the output views now resolve extensions, file types, and tabs from injected editor and extension services.
View wiring
src/Beutl/ViewModels/Dialogs/CreateNew*ViewModel.cs, src/Beutl/Views/Dialogs/CreateNew*.axaml, src/Beutl/Views/MainView.axaml.InitializeMenuBar.cs, src/Beutl/Views/MainView.axaml.cs, src/Beutl/Views/MacWindow.axaml.cs
Create-new dialogs, MainView menu handlers, and MacWindow editor menus now build view models from DataContext and pass EditorContextServices into editor-extension creation.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • b-editor/beutl#1969: Overlaps with src/Beutl/ViewModels/Tools/OutputViewModel.cs and nearby output-loading paths, where encoder and extension lookup behavior also changes.
  • b-editor/beutl#1982: This PR appears to be the current change set and is directly related by content and file overlap.

Poem

I hopped through services, neat and new,
No global stash for me to chew.
Extension clover, editor bright,
The rabbits sing in scoped delight.
🐇🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.12% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: removing the ProjectService, EditorService, and ExtensionProvider singletons.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch yuto-trd/reduce-global-service-locator

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

No TODO comments were found.

@github-actions

Copy link
Copy Markdown
Contributor

Code Coverage

Package Line Rate Branch Rate Complexity Health
Beutl.Api 4% 1% 1160
Beutl.Configuration 41% 22% 350
Beutl.Controls 0% 0% 5513
Beutl.Core 62% 56% 3069
Beutl.Editor 78% 70% 1734
Beutl.Editor.Components 0% 0% 8546
Beutl.Embedding.MediaFoundation 4% 1% 1354
Beutl.Engine 60% 50% 18071
Beutl.Extensibility 38% 59% 112
Beutl.Extensions.AVFoundation 5% 12% 246
Beutl.Extensions.FFmpeg 5% 5% 856
Beutl.FFmpegIpc 22% 27% 799
Beutl.Language 7% 50% 1348
Beutl.NodeGraph 24% 15% 2474
Beutl.ProjectSystem 58% 43% 1061
Beutl.Threading 100% 90% 137
Beutl.Utilities 94% 87% 358
Summary 33% (40937 / 123175) 29% (10243 / 34894) 47188

Minimum allowed line rate is 0%

yuto-trd added 2 commits June 25, 2026 12:06
…letons

Replace the global ProjectService.Current, EditorService.Current, and
ExtensionProvider.Current singletons with constructor injection threaded from the
composition roots. MainViewModel owns the instances and threads them into child
view models, services, views (via DataContext), startup tasks (Startup ->
LoadPrimitiveExtensionTask / RestoreLastProjectTask), settings pages
(SettingsDialogViewModel), and the per-editor tier: EditViewModel exposes
ExtensionProvider/EditorService to DockHostViewModel, OutputViewModel,
OutputService and the property editors.

Editor contexts are created without the global singletons: EditorExtension
.TryCreateContext now receives an IEditorContextServices - a typed host-services
seam in Beutl.Extensibility exposing IExtensionProvider - instead of reaching for
X.Current. ExtensionProvider implements the new IExtensionProvider. The built-in
tutorials receive their services through DefaultTutorialExtension's constructor,
built by LoadPrimitiveExtensionTask.

Refs: Project #9 "AI Review" item "設計改善: グローバル singleton / service locator を減らす"

BREAKING CHANGE: EditorExtension.TryCreateContext gains an IEditorContextServices
parameter as its second argument:
TryCreateContext(CoreObject obj, IEditorContextServices services, out IEditorContext? context).
Implementations must add the parameter; those that need no host services may
ignore it. Use services.ExtensionProvider (IExtensionProvider) to query other
extensions.
The .editorconfig requires charset = utf-8-bom for C# sources, but five files
touched while removing the ProjectService/EditorService/ExtensionProvider
singletons lost their BOM during editing. dotnet format --verify-no-changes
flagged them with CHARSET errors. Re-add the BOM so the changed set passes the
format gate that CI and beutl-reviewer run.
@yuto-trd yuto-trd marked this pull request as ready for review June 25, 2026 04:47
Copilot AI review requested due to automatic review settings June 25, 2026 04:47
@yuto-trd yuto-trd changed the title refactor!: inject extension provider into API application refactor!: remove ProjectService/EditorService/ExtensionProvider singletons Jun 25, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors Beutl’s extension/service wiring to reduce global singleton/service-locator usage by pushing ExtensionProvider (and related editor-session services) into the composition root and threading them through constructors/resource graphs. It also introduces extensibility-facing abstractions (IExtensionProvider, IEditorContextServices) and updates call sites across the app and tooling to pass dependencies explicitly.

Changes:

  • Replace BeutlApiApplication(HttpClient) with BeutlApiApplication(HttpClient, ExtensionProvider) and register the provided provider instance in the API app’s resource graph.
  • Remove/avoid *.Current singletons in several UI/service paths by injecting ExtensionProvider, ProjectService, and EditorService from MainViewModel (composition root), and passing host services to EditorExtension.TryCreateContext.
  • Add unit tests verifying ExtensionProvider implements IExtensionProvider and that the injected provider is reused by PackageManager.

Reviewed changes

Copilot reviewed 81 out of 81 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/PackageSample/SampleEditorExtension.cs Updates sample editor extension to new TryCreateContext(..., IEditorContextServices, ...) signature.
tests/Beutl.UnitTests/Beutl.UnitTests.csproj Adds Beutl.Api project reference for new API-related tests.
tests/Beutl.UnitTests/Api/ExtensionProviderTests.cs Adds tests for IExtensionProvider surface behavior.
tests/Beutl.UnitTests/Api/BeutlApiApplicationTests.cs Adds test ensuring injected ExtensionProvider is registered/reused in API resource graph.
src/Beutl/Views/Tools/OutputView.axaml.cs Switches file-type lookup to instance OutputViewModel method.
src/Beutl/Views/Tools/OutputTab.axaml.cs Uses OutputTabViewModel to obtain extensions (no static/global access).
src/Beutl/Views/MainView.axaml.InitializeMenuBar.cs Wires dialog VMs and editor/project/extension services via MainViewModel instead of globals.
src/Beutl/Views/MainView.axaml.cs Passes EditorContextServices to EditorExtension.TryCreateContext and removes EditorService.Current usage.
src/Beutl/Views/MacWindow.axaml.cs Mirrors MainView menu/service injection changes for macOS native menu wiring.
src/Beutl/Views/Dialogs/CreateNewScene.axaml Removes XAML-created DataContext to allow constructor-injected VM wiring.
src/Beutl/Views/Dialogs/CreateNewProject.axaml Removes XAML-created DataContext to allow constructor-injected VM wiring.
src/Beutl/ViewModels/Tools/OutputViewModel.cs Threads ExtensionProvider into encoder settings VMs and removes ExtensionProvider.Current usage.
src/Beutl/ViewModels/Tools/OutputTabViewModel.cs Exposes output extensions via instance method to avoid removed singleton access.
src/Beutl/ViewModels/SettingsPages/KeyMapSettingsPageViewModel.cs Injects ExtensionProvider to resolve extensions for keymap grouping.
src/Beutl/ViewModels/SettingsPages/ExtensionsSettingsPageViewModel.cs Injects ExtensionProvider and threads it into child settings VMs.
src/Beutl/ViewModels/SettingsPages/EditorExtensionPriorityPageViewModel.cs Injects ExtensionProvider for editor-extension priority display mapping.
src/Beutl/ViewModels/SettingsPages/AnExtensionSettingsPageViewModel.cs Injects ExtensionProvider into property editor matching for extension settings.
src/Beutl/ViewModels/SettingsDialogViewModel.cs Requires ExtensionProvider so settings sub-pages can avoid singleton lookups.
src/Beutl/ViewModels/MenuBarViewModel.View.cs Converts reset handler to instance method (removes reliance on globals).
src/Beutl/ViewModels/MenuBarViewModel.Scene.cs Uses injected _projectService / _editorService instead of *.Current.
src/Beutl/ViewModels/MenuBarViewModel.Files.cs Uses injected services; makes OpenFileCore instance-based.
src/Beutl/ViewModels/MenuBarViewModel.cs Adds constructor injection for ProjectService and EditorService.
src/Beutl/ViewModels/MainViewModel.cs Becomes the composition root for editor-session services and passes them down.
src/Beutl/ViewModels/ExtensionsPageViewModel.cs Threads editor/project services into discover/library page creation.
src/Beutl/ViewModels/ExtensionsPages/RemoteUserPackageViewModel.cs Uses instance PackageOperationHandler (with injected services) for project-close checks.
src/Beutl/ViewModels/ExtensionsPages/PackageOperationHandler.cs Removes static global access; operates via injected ProjectService/EditorService.
src/Beutl/ViewModels/ExtensionsPages/LocalUserPackageViewModel.cs Same handler/service threading as remote package VM.
src/Beutl/ViewModels/ExtensionsPages/LibraryPageViewModel.cs Threads editor/project services into per-package VMs.
src/Beutl/ViewModels/ExtensionsPages/DiscoverPageViewModel.cs Threads editor/project services into discover page factory.
src/Beutl/ViewModels/ExtensionsPages/DiscoverPages/PackageDetailsPageViewModel.cs Threads editor/project services into package operation handler.
src/Beutl/ViewModels/ExtensionsPages/DiscoverPages/DataContextFactory.cs Extends factory to carry editor/project services to created pages.
src/Beutl/ViewModels/EncoderSettingsViewModel.cs Injects ExtensionProvider for property editor matching (no singleton).
src/Beutl/ViewModels/EditViewModel.cs Accepts injected ExtensionProvider/EditorService; provides non-singleton property editor factories.
src/Beutl/ViewModels/Editors/TransformEditorViewModel.cs Creates PropertiesEditorViewModel with injected provider.
src/Beutl/ViewModels/Editors/TextureSourceEditorViewModel.cs Gates child-context creation on injected provider availability.
src/Beutl/ViewModels/Editors/PropertiesEditorViewModel.cs Requires ExtensionProvider and uses it for property editor matching.
src/Beutl/ViewModels/Editors/PenEditorViewModel.cs Gates eager subscriptions on provider injection; passes provider to matching.
src/Beutl/ViewModels/Editors/PathOperationEditorViewModel.cs Creates PropertiesEditorViewModel with injected provider.
src/Beutl/ViewModels/Editors/PathFigureEditorViewModel.cs Creates PropertiesEditorViewModel with injected provider and predicates.
src/Beutl/ViewModels/Editors/ListEditorViewModel.cs Routes list-item property editor matching through injected IPropertyEditorFactory.
src/Beutl/ViewModels/Editors/GraphModelNodeMemberViewModel.cs Uses injected IPropertyEditorFactory rather than static matching.
src/Beutl/ViewModels/Editors/GraphModelEditorViewModel.cs Gates construction on provider injection to avoid premature factory resolution.
src/Beutl/ViewModels/Editors/GeometryEditorViewModel.cs Creates PropertiesEditorViewModel with injected provider.
src/Beutl/ViewModels/Editors/FilterEffectEditorViewModel.cs Creates PropertiesEditorViewModel with injected provider.
src/Beutl/ViewModels/Editors/DisplacementMapTransformEditorViewModel.cs Creates PropertiesEditorViewModel with injected provider.
src/Beutl/ViewModels/Editors/CoreObjectEditorViewModel.cs Gates child-context creation on provider injection and disposes previous values.
src/Beutl/ViewModels/Editors/BrushEditorViewModel.cs Gates child-context creation on provider injection and disposes previous values.
src/Beutl/ViewModels/Editors/BaseEditorViewModel.cs Adds injected-provider plumbing (GetExtensionProvider / ObserveExtensionProvider) populated via Accept.
src/Beutl/ViewModels/Editors/AudioEffectEditorViewModel.cs Creates PropertiesEditorViewModel with injected provider.
src/Beutl/ViewModels/EditorHostViewModel.cs Accepts injected ProjectService/EditorService instead of using globals.
src/Beutl/ViewModels/DockHostViewModel.cs Uses injected EditViewModel.ExtensionProvider for tool-tab extension discovery/restore.
src/Beutl/ViewModels/Dialogs/CreateNewSceneViewModel.cs Injects project/editor services for scene creation and activation.
src/Beutl/ViewModels/Dialogs/CreateNewProjectViewModel.cs Injects ProjectService for project creation.
src/Beutl/ViewModels/Dialogs/AddOutputProfileViewModel.cs Uses OutputTabViewModel.GetExtensions rather than static lookup.
src/Beutl/ViewModels/CommandPaletteViewModel.cs Uses injected EditorService for active-tab tracking.
src/Beutl/Services/Tutorials/TutorialHelpers.cs Removes globals by taking ProjectService/EditorService parameters.
src/Beutl/Services/Tutorials/TimelineBasicsTutorial.cs Wires tutorial prerequisites/actions via injected services.
src/Beutl/Services/Tutorials/AnimationEditTutorial.cs Wires tutorial prerequisites/actions via injected services.
src/Beutl/Services/StartupTasks/Startup.cs Threads editor/project services to tasks that need them; updates task construction.
src/Beutl/Services/StartupTasks/RestoreLastProjectTask.cs Uses injected ProjectService for restore logic.
src/Beutl/Services/StartupTasks/LoadPrimitiveExtensionTask.cs Stops using ExtensionProvider.Current; builds tutorial extension with injected services.
src/Beutl/Services/PropertyEditorService.cs Requires an explicit provider for matching property editor extensions.
src/Beutl/Services/ProjectService.cs Removes ProjectService.Current singleton.
src/Beutl/Services/PrimitiveImpls/SceneEditorExtension.cs Updates editor context creation to accept host services and inject into EditViewModel.
src/Beutl/Services/PrimitiveImpls/ExtensionsToolWindowExtension.cs Creates Extensions page VM with injected editor/project services.
src/Beutl/Services/PrimitiveImpls/DefaultTutorialExtension.cs Replaces static singleton with constructor-injected services.
src/Beutl/Services/OutputService.cs Removes EditorService.Current/ExtensionProvider.Current usage via injected fields/params.
src/Beutl/Services/EditorService.cs Removes EditorService.Current; injects ExtensionProvider and passes IEditorContextServices to extensions.
src/Beutl/Services/EditorContextServices.cs Adds host implementation of IEditorContextServices to pass to editor extensions.
src/Beutl/Services/CommandPaletteService.cs Injects editor/extension services and removes global lookups.
src/Beutl/Services/CommandPaletteHandlerProvider.cs Injects EditorService to resolve context handlers without globals.
src/Beutl/Services/Adapters/PropertyEditorFactoryAdapter.cs Removes static singleton factory; captures provider instance for matching.
src/Beutl/Services/Adapters/PropertiesEditorFactoryImpl.cs Removes static singleton; captures provider instance for creating editor VMs.
src/Beutl/Pages/SettingsPages/ExtensionsSettingsPage.axaml Removes XAML-created DataContext to support injected VM creation.
src/Beutl/App.axaml.cs Removes tutorial wiring that depended on static singleton tutorial extension.
src/Beutl.PackageTools.UI/ViewModels/MainViewModel.cs Updates package tools to pass an explicit ExtensionProvider into BeutlApiApplication.
src/Beutl.Extensibility/IExtensionProvider.cs Introduces read-only extension-provider abstraction for extension authors.
src/Beutl.Extensibility/IEditorContextServices.cs Introduces host-services abstraction for EditorExtension.TryCreateContext.
src/Beutl.Extensibility/EditorExtension.cs Updates editor context creation API to accept host services.
src/Beutl.Api/Services/ExtensionProvider.cs Implements IExtensionProvider and removes ExtensionProvider.Current singleton.
src/Beutl.Api/BeutlApiApplication.cs Injects ExtensionProvider via ctor and registers it in the resource graph.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/Beutl/Services/PropertyEditorService.cs

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5f72dad53a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/Beutl/ViewModels/Editors/BaseEditorViewModel.cs Outdated
Comment thread src/Beutl/ViewModels/Editors/ListEditorViewModel.cs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/Beutl/Services/StartupTasks/LoadPrimitiveExtensionTask.cs (1)

60-82: 📐 Maintainability & Code Quality | 🟡 Minor

Add NUnit coverage for the injected primitive extension set

LoadPrimitiveExtensionTask now appends DefaultTutorialExtension and registers allExtensions, but there’s no NUnit test under tests/ covering that wiring. Add a test that asserts the tutorial extension is included and built with the provided EditorService and ProjectService.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Beutl/Services/StartupTasks/LoadPrimitiveExtensionTask.cs` around lines
60 - 82, Add NUnit coverage for LoadPrimitiveExtensionTask’s primitive extension
wiring by testing that allExtensions includes DefaultTutorialExtension and that
it is constructed with the provided EditorService and ProjectService. Use the
LoadPrimitiveExtensionTask constructor as the entry point, and assert the
injected services are the ones passed through to the tutorial extension when
provider.AddExtensions is reached.

Source: Coding guidelines

🧹 Nitpick comments (3)
src/Beutl.Api/BeutlApiApplication.cs (1)

55-61: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Consider guarding httpClient too.

extensionProvider gets ArgumentNullException.ThrowIfNull, but httpClient is dereferenced immediately at Line 61 (httpClient.BaseAddress). A null httpClient throws an opaque NullReferenceException deep in the constructor instead of a clear argument error. Adding a matching guard keeps the contract consistent.

♻️ Proposed guard
     public BeutlApiApplication(HttpClient httpClient, ExtensionProvider extensionProvider)
     {
+        ArgumentNullException.ThrowIfNull(httpClient);
         ArgumentNullException.ThrowIfNull(extensionProvider);

         _httpClient = httpClient;
         _extensionProvider = extensionProvider;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Beutl.Api/BeutlApiApplication.cs` around lines 55 - 61, The constructor
in BeutlApiApplication currently guards extensionProvider but not httpClient, so
a null client will fail later when BaseAddress is assigned. Add a matching null
check for httpClient at the start of the BeutlApiApplication(HttpClient
httpClient, ExtensionProvider extensionProvider) constructor, alongside the
existing ArgumentNullException.ThrowIfNull call, so both dependencies fail fast
with a clear argument error.
src/Beutl/ViewModels/EditViewModel.cs (1)

55-61: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider null-guarding the injected services for fail-fast consistency.

EditorService (this PR) guards its injected ExtensionProvider with ArgumentNullException.ThrowIfNull. The EditViewModel constructor stores extensionProvider/editorService without a guard, so a null would only surface much later (e.g. when a property editor calls GetExtensionProvider()). A guard here makes the failure point match the rest of the refactor.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Beutl/ViewModels/EditViewModel.cs` around lines 55 - 61, Add fail-fast
null checks in the EditViewModel constructor before assigning the injected
dependencies. Mirror the existing pattern used in EditorService by guarding
extensionProvider and editorService (and any other required constructor inputs
if needed) with ArgumentNullException.ThrowIfNull so a null is rejected
immediately; locate this in the EditViewModel constructor where Scene,
ExtensionProvider, and EditorService are initialized.
src/Beutl/Services/PropertyEditorService.cs (1)

31-34: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Optional: guard against a null extensionProvider.

MatchProperty immediately dereferences extensionProvider.GetExtensions<...>(). All current callers pass an injected non-null instance, but an ArgumentNullException.ThrowIfNull(extensionProvider) would fail fast with a clearer signal than a raw NRE if a future caller forgets to wire it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Beutl/Services/PropertyEditorService.cs` around lines 31 - 34,
`PropertyEditorService.MatchProperty` currently dereferences `extensionProvider`
immediately via `GetExtensions<PropertyEditorExtension>()`, so add a fail-fast
null check at the start of the method; use
`ArgumentNullException.ThrowIfNull(extensionProvider)` (or equivalent) before
any property/extension lookup so future callers get a clear argument error
instead of a null reference.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/Beutl.Extensibility/EditorExtension.cs`:
- Around line 33-36: Update the public signature change in
EditorExtension.TryCreateContext to be explicitly documented as a breaking
extensibility change, since adding IEditorContextServices affects all
out-of-tree overrides. Add a refactor!/BREAKING CHANGE note near the
TryCreateContext declaration and include a short migration hint telling
extension authors to update their EditorExtension overrides to accept the new
services parameter and use it when creating IEditorContext instances.

In `@src/Beutl/ViewModels/SettingsPages/KeyMapSettingsPageViewModel.cs`:
- Around line 58-67: The KeyMapSettingsPageViewModel constructor currently
assumes every command definition’s ExtensionType exists in
ExtensionProvider.AllExtensions via First, which can throw when they drift.
Update the grouping logic in KeyMapSettingsPageViewModel to safely handle
missing injected extensions by skipping unmatched groups or otherwise guarding
the lookup instead of throwing. Add a matching NUnit test under tests/ for this
constructor behavior to verify Key Map no longer crashes when a
ContextCommandManager definition has no corresponding extension.

---

Outside diff comments:
In `@src/Beutl/Services/StartupTasks/LoadPrimitiveExtensionTask.cs`:
- Around line 60-82: Add NUnit coverage for LoadPrimitiveExtensionTask’s
primitive extension wiring by testing that allExtensions includes
DefaultTutorialExtension and that it is constructed with the provided
EditorService and ProjectService. Use the LoadPrimitiveExtensionTask constructor
as the entry point, and assert the injected services are the ones passed through
to the tutorial extension when provider.AddExtensions is reached.

---

Nitpick comments:
In `@src/Beutl.Api/BeutlApiApplication.cs`:
- Around line 55-61: The constructor in BeutlApiApplication currently guards
extensionProvider but not httpClient, so a null client will fail later when
BaseAddress is assigned. Add a matching null check for httpClient at the start
of the BeutlApiApplication(HttpClient httpClient, ExtensionProvider
extensionProvider) constructor, alongside the existing
ArgumentNullException.ThrowIfNull call, so both dependencies fail fast with a
clear argument error.

In `@src/Beutl/Services/PropertyEditorService.cs`:
- Around line 31-34: `PropertyEditorService.MatchProperty` currently
dereferences `extensionProvider` immediately via
`GetExtensions<PropertyEditorExtension>()`, so add a fail-fast null check at the
start of the method; use `ArgumentNullException.ThrowIfNull(extensionProvider)`
(or equivalent) before any property/extension lookup so future callers get a
clear argument error instead of a null reference.

In `@src/Beutl/ViewModels/EditViewModel.cs`:
- Around line 55-61: Add fail-fast null checks in the EditViewModel constructor
before assigning the injected dependencies. Mirror the existing pattern used in
EditorService by guarding extensionProvider and editorService (and any other
required constructor inputs if needed) with ArgumentNullException.ThrowIfNull so
a null is rejected immediately; locate this in the EditViewModel constructor
where Scene, ExtensionProvider, and EditorService are initialized.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 85cbde9e-b006-4fcc-901d-21ceb4e8e29d

📥 Commits

Reviewing files that changed from the base of the PR and between ca7208b and 5f72dad.

📒 Files selected for processing (81)
  • src/Beutl.Api/BeutlApiApplication.cs
  • src/Beutl.Api/Services/ExtensionProvider.cs
  • src/Beutl.Extensibility/EditorExtension.cs
  • src/Beutl.Extensibility/IEditorContextServices.cs
  • src/Beutl.Extensibility/IExtensionProvider.cs
  • src/Beutl.PackageTools.UI/ViewModels/MainViewModel.cs
  • src/Beutl/App.axaml.cs
  • src/Beutl/Pages/SettingsPages/ExtensionsSettingsPage.axaml
  • src/Beutl/Services/Adapters/PropertiesEditorFactoryImpl.cs
  • src/Beutl/Services/Adapters/PropertyEditorFactoryAdapter.cs
  • src/Beutl/Services/CommandPaletteHandlerProvider.cs
  • src/Beutl/Services/CommandPaletteService.cs
  • src/Beutl/Services/EditorContextServices.cs
  • src/Beutl/Services/EditorService.cs
  • src/Beutl/Services/OutputService.cs
  • src/Beutl/Services/PrimitiveImpls/DefaultTutorialExtension.cs
  • src/Beutl/Services/PrimitiveImpls/ExtensionsToolWindowExtension.cs
  • src/Beutl/Services/PrimitiveImpls/SceneEditorExtension.cs
  • src/Beutl/Services/ProjectService.cs
  • src/Beutl/Services/PropertyEditorService.cs
  • src/Beutl/Services/StartupTasks/LoadPrimitiveExtensionTask.cs
  • src/Beutl/Services/StartupTasks/RestoreLastProjectTask.cs
  • src/Beutl/Services/StartupTasks/Startup.cs
  • src/Beutl/Services/Tutorials/AnimationEditTutorial.cs
  • src/Beutl/Services/Tutorials/TimelineBasicsTutorial.cs
  • src/Beutl/Services/Tutorials/TutorialHelpers.cs
  • src/Beutl/ViewModels/CommandPaletteViewModel.cs
  • src/Beutl/ViewModels/Dialogs/AddOutputProfileViewModel.cs
  • src/Beutl/ViewModels/Dialogs/CreateNewProjectViewModel.cs
  • src/Beutl/ViewModels/Dialogs/CreateNewSceneViewModel.cs
  • src/Beutl/ViewModels/DockHostViewModel.cs
  • src/Beutl/ViewModels/EditViewModel.cs
  • src/Beutl/ViewModels/EditorHostViewModel.cs
  • src/Beutl/ViewModels/Editors/AudioEffectEditorViewModel.cs
  • src/Beutl/ViewModels/Editors/BaseEditorViewModel.cs
  • src/Beutl/ViewModels/Editors/BrushEditorViewModel.cs
  • src/Beutl/ViewModels/Editors/CoreObjectEditorViewModel.cs
  • src/Beutl/ViewModels/Editors/DisplacementMapTransformEditorViewModel.cs
  • src/Beutl/ViewModels/Editors/FilterEffectEditorViewModel.cs
  • src/Beutl/ViewModels/Editors/GeometryEditorViewModel.cs
  • src/Beutl/ViewModels/Editors/GraphModelEditorViewModel.cs
  • src/Beutl/ViewModels/Editors/GraphModelNodeMemberViewModel.cs
  • src/Beutl/ViewModels/Editors/ListEditorViewModel.cs
  • src/Beutl/ViewModels/Editors/PathFigureEditorViewModel.cs
  • src/Beutl/ViewModels/Editors/PathOperationEditorViewModel.cs
  • src/Beutl/ViewModels/Editors/PenEditorViewModel.cs
  • src/Beutl/ViewModels/Editors/PropertiesEditorViewModel.cs
  • src/Beutl/ViewModels/Editors/TextureSourceEditorViewModel.cs
  • src/Beutl/ViewModels/Editors/TransformEditorViewModel.cs
  • src/Beutl/ViewModels/EncoderSettingsViewModel.cs
  • src/Beutl/ViewModels/ExtensionsPageViewModel.cs
  • src/Beutl/ViewModels/ExtensionsPages/DiscoverPageViewModel.cs
  • src/Beutl/ViewModels/ExtensionsPages/DiscoverPages/DataContextFactory.cs
  • src/Beutl/ViewModels/ExtensionsPages/DiscoverPages/PackageDetailsPageViewModel.cs
  • src/Beutl/ViewModels/ExtensionsPages/LibraryPageViewModel.cs
  • src/Beutl/ViewModels/ExtensionsPages/LocalUserPackageViewModel.cs
  • src/Beutl/ViewModels/ExtensionsPages/PackageOperationHandler.cs
  • src/Beutl/ViewModels/ExtensionsPages/RemoteUserPackageViewModel.cs
  • src/Beutl/ViewModels/MainViewModel.cs
  • src/Beutl/ViewModels/MenuBarViewModel.Files.cs
  • src/Beutl/ViewModels/MenuBarViewModel.Scene.cs
  • src/Beutl/ViewModels/MenuBarViewModel.View.cs
  • src/Beutl/ViewModels/MenuBarViewModel.cs
  • src/Beutl/ViewModels/SettingsDialogViewModel.cs
  • src/Beutl/ViewModels/SettingsPages/AnExtensionSettingsPageViewModel.cs
  • src/Beutl/ViewModels/SettingsPages/EditorExtensionPriorityPageViewModel.cs
  • src/Beutl/ViewModels/SettingsPages/ExtensionsSettingsPageViewModel.cs
  • src/Beutl/ViewModels/SettingsPages/KeyMapSettingsPageViewModel.cs
  • src/Beutl/ViewModels/Tools/OutputTabViewModel.cs
  • src/Beutl/ViewModels/Tools/OutputViewModel.cs
  • src/Beutl/Views/Dialogs/CreateNewProject.axaml
  • src/Beutl/Views/Dialogs/CreateNewScene.axaml
  • src/Beutl/Views/MacWindow.axaml.cs
  • src/Beutl/Views/MainView.axaml.InitializeMenuBar.cs
  • src/Beutl/Views/MainView.axaml.cs
  • src/Beutl/Views/Tools/OutputTab.axaml.cs
  • src/Beutl/Views/Tools/OutputView.axaml.cs
  • tests/Beutl.UnitTests/Api/BeutlApiApplicationTests.cs
  • tests/Beutl.UnitTests/Api/ExtensionProviderTests.cs
  • tests/Beutl.UnitTests/Beutl.UnitTests.csproj
  • tests/PackageSample/SampleEditorExtension.cs
💤 Files with no reviewable changes (4)
  • src/Beutl/Pages/SettingsPages/ExtensionsSettingsPage.axaml
  • src/Beutl/Views/Dialogs/CreateNewProject.axaml
  • src/Beutl/Views/Dialogs/CreateNewScene.axaml
  • src/Beutl/Services/ProjectService.cs

Comment thread src/Beutl.Extensibility/EditorExtension.cs
Comment thread src/Beutl/ViewModels/SettingsPages/KeyMapSettingsPageViewModel.cs
…n-scene hosts

Address PR review findings on the service-locator removal:

- Property editors hosted outside a scene tab (encoder settings, extension
  settings) no longer received the ExtensionProvider or IPropertyEditorFactory,
  so nested object/list editors threw or failed to render when expanded. The
  hosts now expose these via GetService, and BaseEditorViewModel resolves the
  provider from the service chain instead of only via EditViewModel.
- KeyMapSettingsPageViewModel used First() on the extension lookup, which threw
  when a command definition's ExtensionType had no registered extension; use
  FirstOrDefault and skip unmatched groups.
- PropertyEditorService.MatchProperty now takes IExtensionProvider to depend on
  the abstraction rather than the concrete API-layer type.
- Add fail-fast ArgumentNullException guards (BeutlApiApplication.httpClient,
  EditViewModel ctor, MatchProperty) with tests for the API constructor.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0cba4ac1a5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

var visitor = new Visitor(this);
var itemAccessor = new ListItemAccessorImpl<TItem?>(index, obj, List.Value!);
var item = new ListItemEditorViewModel<TItem>(this, itemAccessor);
var item = new ListItemEditorViewModel<TItem>(this, itemAccessor, this.GetRequiredService<IPropertyEditorFactory>());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Defer list factory resolution until after Accept

When a nested list editor is initialized already expanded (for example the audio/filter/transform/path group editors create child ListEditorViewModels with IsExpanded = true before AcceptChild() accepts the child), the expansion subscription immediately replays the current list and reaches this line before _parentServices has been set by Accept. Because the new factory lookup depends on that service chain, expanding a group that already has items throws instead of rendering its children. This is fresh evidence beyond the settings-host comment: the same diff creates pre-Accept expanded child lists in normal scene editors.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/Beutl/ViewModels/SettingsPages/AnExtensionSettingsPageViewModel.cs (1)

57-58: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Resolve the provider by its abstraction as well.

This service host should return the injected provider when callers request IExtensionProvider, not just the concrete ExtensionProvider.

Proposed fix
-        if (serviceType == typeof(ExtensionProvider))
+        if (serviceType == typeof(ExtensionProvider) || serviceType == typeof(IExtensionProvider))
             return _extensionProvider;

As per coding guidelines, **/*.cs: New logic must ship with a NUnit test under tests/ in the matching test project.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Beutl/ViewModels/SettingsPages/AnExtensionSettingsPageViewModel.cs`
around lines 57 - 58, The service resolution in AnExtensionSettingsPageViewModel
only handles the concrete ExtensionProvider type, so callers asking for
IExtensionProvider are not satisfied. Update the provider lookup logic in the
service-host implementation to return the injected instance for both the
abstraction and the concrete type, and add a matching NUnit test under tests/ to
cover resolving IExtensionProvider.

Source: Coding guidelines

src/Beutl/ViewModels/EncoderSettingsViewModel.cs (1)

45-46: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Expose the provider through IExtensionProvider too.

The new provider abstraction can be requested through the service chain, but this host currently only resolves the concrete ExtensionProvider.

Proposed fix
-        if (serviceType == typeof(ExtensionProvider))
+        if (serviceType == typeof(ExtensionProvider) || serviceType == typeof(IExtensionProvider))
             return _extensionProvider;

As per coding guidelines, **/*.cs: New logic must ship with a NUnit test under tests/ in the matching test project.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Beutl/ViewModels/EncoderSettingsViewModel.cs` around lines 45 - 46, The
service resolution in EncoderSettingsViewModel only returns the concrete
ExtensionProvider, so extend the existing GetService logic to also return the
same instance when the requested type is IExtensionProvider. Update the
service-chain handling in the relevant method so both symbols are supported
without changing the provider instance. Add a matching NUnit test under tests/
for the corresponding view model/service resolution path to verify
IExtensionProvider is resolved successfully.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/Beutl/ViewModels/EncoderSettingsViewModel.cs`:
- Around line 45-46: The service resolution in EncoderSettingsViewModel only
returns the concrete ExtensionProvider, so extend the existing GetService logic
to also return the same instance when the requested type is IExtensionProvider.
Update the service-chain handling in the relevant method so both symbols are
supported without changing the provider instance. Add a matching NUnit test
under tests/ for the corresponding view model/service resolution path to verify
IExtensionProvider is resolved successfully.

In `@src/Beutl/ViewModels/SettingsPages/AnExtensionSettingsPageViewModel.cs`:
- Around line 57-58: The service resolution in AnExtensionSettingsPageViewModel
only handles the concrete ExtensionProvider type, so callers asking for
IExtensionProvider are not satisfied. Update the provider lookup logic in the
service-host implementation to return the injected instance for both the
abstraction and the concrete type, and add a matching NUnit test under tests/ to
cover resolving IExtensionProvider.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0569f1a1-9835-45b4-ae50-4e9069280b4d

📥 Commits

Reviewing files that changed from the base of the PR and between 5f72dad and 0cba4ac.

📒 Files selected for processing (8)
  • src/Beutl.Api/BeutlApiApplication.cs
  • src/Beutl/Services/PropertyEditorService.cs
  • src/Beutl/ViewModels/EditViewModel.cs
  • src/Beutl/ViewModels/Editors/BaseEditorViewModel.cs
  • src/Beutl/ViewModels/EncoderSettingsViewModel.cs
  • src/Beutl/ViewModels/SettingsPages/AnExtensionSettingsPageViewModel.cs
  • src/Beutl/ViewModels/SettingsPages/KeyMapSettingsPageViewModel.cs
  • tests/Beutl.UnitTests/Api/BeutlApiApplicationTests.cs
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/Beutl/Services/PropertyEditorService.cs
  • src/Beutl/ViewModels/SettingsPages/KeyMapSettingsPageViewModel.cs
  • src/Beutl.Api/BeutlApiApplication.cs
  • src/Beutl/ViewModels/Editors/BaseEditorViewModel.cs
  • src/Beutl/ViewModels/EditViewModel.cs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants